##// END OF EJS Templates
Merge of the ipython-ipython1a branch into the ipython trunk. This merge represents the first...
Brian E Granger -
r1247:48fb9d90 merge
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,14 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """This is the official entry point to IPython's configuration system. """
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 from IPython.config.cutils import get_home_dir, get_ipython_dir
20 from IPython.external.configobj import ConfigObj
21
22 class ConfigObjManager(object):
23
24 def __init__(self, configObj, filename):
25 self.current = configObj
26 self.current.indent_type = ' '
27 self.filename = filename
28 # self.write_default_config_file()
29
30 def get_config_obj(self):
31 return self.current
32
33 def update_config_obj(self, newConfig):
34 self.current.merge(newConfig)
35
36 def update_config_obj_from_file(self, filename):
37 newConfig = ConfigObj(filename, file_error=False)
38 self.current.merge(newConfig)
39
40 def update_config_obj_from_default_file(self, ipythondir=None):
41 fname = self.resolve_file_path(self.filename, ipythondir)
42 self.update_config_obj_from_file(fname)
43
44 def write_config_obj_to_file(self, filename):
45 f = open(filename, 'w')
46 self.current.write(f)
47 f.close()
48
49 def write_default_config_file(self):
50 ipdir = get_ipython_dir()
51 fname = ipdir + '/' + self.filename
52 if not os.path.isfile(fname):
53 print "Writing the configuration file to: " + fname
54 self.write_config_obj_to_file(fname)
55
56 def _import(self, key):
57 package = '.'.join(key.split('.')[0:-1])
58 obj = key.split('.')[-1]
59 execString = 'from %s import %s' % (package, obj)
60 exec execString
61 exec 'temp = %s' % obj
62 return temp
63
64 def resolve_file_path(self, filename, ipythondir = None):
65 """Resolve filenames into absolute paths.
66
67 This function looks in the following directories in order:
68
69 1. In the current working directory or by absolute path with ~ expanded
70 2. In ipythondir if that is set
71 3. In the IPYTHONDIR environment variable if it exists
72 4. In the ~/.ipython directory
73
74 Note: The IPYTHONDIR is also used by the trunk version of IPython so
75 changing it will also affect it was well.
76 """
77
78 # In cwd or by absolute path with ~ expanded
79 trythis = os.path.expanduser(filename)
80 if os.path.isfile(trythis):
81 return trythis
82
83 # In ipythondir if it is set
84 if ipythondir is not None:
85 trythis = ipythondir + '/' + filename
86 if os.path.isfile(trythis):
87 return trythis
88
89 trythis = get_ipython_dir() + '/' + filename
90 if os.path.isfile(trythis):
91 return trythis
92
93 return None
94
95
96
97
98
99
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """Configuration-related utilities for all IPython."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 import sys
20
21 #---------------------------------------------------------------------------
22 # Normal code begins
23 #---------------------------------------------------------------------------
24
25 class HomeDirError(Exception):
26 pass
27
28 def get_home_dir():
29 """Return the closest possible equivalent to a 'home' directory.
30
31 We first try $HOME. Absent that, on NT it's $HOMEDRIVE\$HOMEPATH.
32
33 Currently only Posix and NT are implemented, a HomeDirError exception is
34 raised for all other OSes. """
35
36 isdir = os.path.isdir
37 env = os.environ
38 try:
39 homedir = env['HOME']
40 if not isdir(homedir):
41 # in case a user stuck some string which does NOT resolve to a
42 # valid path, it's as good as if we hadn't foud it
43 raise KeyError
44 return homedir
45 except KeyError:
46 if os.name == 'posix':
47 raise HomeDirError,'undefined $HOME, IPython can not proceed.'
48 elif os.name == 'nt':
49 # For some strange reason, win9x returns 'nt' for os.name.
50 try:
51 homedir = os.path.join(env['HOMEDRIVE'],env['HOMEPATH'])
52 if not isdir(homedir):
53 homedir = os.path.join(env['USERPROFILE'])
54 if not isdir(homedir):
55 raise HomeDirError
56 return homedir
57 except:
58 try:
59 # Use the registry to get the 'My Documents' folder.
60 import _winreg as wreg
61 key = wreg.OpenKey(wreg.HKEY_CURRENT_USER,
62 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
63 homedir = wreg.QueryValueEx(key,'Personal')[0]
64 key.Close()
65 if not isdir(homedir):
66 e = ('Invalid "Personal" folder registry key '
67 'typically "My Documents".\n'
68 'Value: %s\n'
69 'This is not a valid directory on your system.' %
70 homedir)
71 raise HomeDirError(e)
72 return homedir
73 except HomeDirError:
74 raise
75 except:
76 return 'C:\\'
77 elif os.name == 'dos':
78 # Desperate, may do absurd things in classic MacOS. May work under DOS.
79 return 'C:\\'
80 else:
81 raise HomeDirError,'support for your operating system not implemented.'
82
83 def get_ipython_dir():
84 ipdir_def = '.ipython'
85 home_dir = get_home_dir()
86 ipdir = os.path.abspath(os.environ.get('IPYTHONDIR',
87 os.path.join(home_dir,ipdir_def)))
88 return ipdir
89
90 def import_item(key):
91 """
92 Import and return bar given the string foo.bar.
93 """
94 package = '.'.join(key.split('.')[0:-1])
95 obj = key.split('.')[-1]
96 execString = 'from %s import %s' % (package, obj)
97 exec execString
98 exec 'temp = %s' % obj
99 return temp
This diff has been collapsed as it changes many lines, (622 lines changed) Show them Hide them
@@ -0,0 +1,622 b''
1 # encoding: utf-8
2
3 """Mix of ConfigObj and Struct-like access.
4
5 Provides:
6
7 - Coupling a Struct object to a ConfigObj one, so that changes to the Traited
8 instance propagate back into the ConfigObj.
9
10 - A declarative interface for describing configurations that automatically maps
11 to valid ConfigObj representations.
12
13 - From these descriptions, valid .conf files can be auto-generated, with class
14 docstrings and traits information used for initial auto-documentation.
15
16 - Hierarchical inclusion of files, so that a base config can be overridden only
17 in specific spots.
18
19
20 Notes:
21
22 The file creation policy is:
23
24 1. Creating a SConfigManager(FooConfig,'missingfile.conf') will work
25 fine, and 'missingfile.conf' will be created empty.
26
27 2. Creating SConfigManager(FooConfig,'OKfile.conf') where OKfile.conf has
28
29 include = 'missingfile.conf'
30
31 conks out with IOError.
32
33 My rationale is that creating top-level empty files is a common and
34 reasonable need, but that having invalid include statements should
35 raise an error right away, so people know immediately that their files
36 have gone stale.
37
38
39 TODO:
40
41 - Turn the currently interactive tests into proper doc/unit tests. Complete
42 docstrings.
43
44 - Write the real ipython1 config system using this. That one is more
45 complicated than either the MPL one or the fake 'ipythontest' that I wrote
46 here, and it requires solving the issue of declaring references to other
47 objects inside the config files.
48
49 - [Low priority] Write a custom TraitsUI view so that hierarchical
50 configurations provide nicer interactive editing. The automatic system is
51 remarkably good, but for very complex configurations having a nicely
52 organized view would be nice.
53 """
54
55 __docformat__ = "restructuredtext en"
56 __license__ = 'BSD'
57
58 #-------------------------------------------------------------------------------
59 # Copyright (C) 2008 The IPython Development Team
60 #
61 # Distributed under the terms of the BSD License. The full license is in
62 # the file COPYING, distributed as part of this software.
63 #-------------------------------------------------------------------------------
64
65 #-------------------------------------------------------------------------------
66 # Imports
67 #-------------------------------------------------------------------------------
68
69 ############################################################################
70 # Stdlib imports
71 ############################################################################
72 from cStringIO import StringIO
73 from inspect import isclass
74
75 import os
76 import textwrap
77
78 ############################################################################
79 # External imports
80 ############################################################################
81
82 from IPython.external import configobj
83
84 ############################################################################
85 # Utility functions
86 ############################################################################
87
88 def get_split_ind(seq, N):
89 """seq is a list of words. Return the index into seq such that
90 len(' '.join(seq[:ind])<=N
91 """
92
93 sLen = 0
94 # todo: use Alex's xrange pattern from the cbook for efficiency
95 for (word, ind) in zip(seq, range(len(seq))):
96 sLen += len(word) + 1 # +1 to account for the len(' ')
97 if sLen>=N: return ind
98 return len(seq)
99
100 def wrap(prefix, text, cols, max_lines=6):
101 """'wrap text with prefix at length cols"""
102 pad = ' '*len(prefix.expandtabs())
103 available = cols - len(pad)
104
105 seq = text.split(' ')
106 Nseq = len(seq)
107 ind = 0
108 lines = []
109 while ind<Nseq:
110 lastInd = ind
111 ind += get_split_ind(seq[ind:], available)
112 lines.append(seq[lastInd:ind])
113
114 num_lines = len(lines)
115 abbr_end = max_lines // 2
116 abbr_start = max_lines - abbr_end
117 lines_skipped = False
118 for i in range(num_lines):
119 if i == 0:
120 # add the prefix to the first line, pad with spaces otherwise
121 ret = prefix + ' '.join(lines[i]) + '\n'
122 elif i < abbr_start or i > num_lines-abbr_end-1:
123 ret += pad + ' '.join(lines[i]) + '\n'
124 else:
125 if not lines_skipped:
126 lines_skipped = True
127 ret += ' <...snipped %d lines...> \n' % (num_lines-max_lines)
128 # for line in lines[1:]:
129 # ret += pad + ' '.join(line) + '\n'
130 return ret[:-1]
131
132 def dedent(txt):
133 """A modified version of textwrap.dedent, specialized for docstrings.
134
135 This version doesn't get confused by the first line of text having
136 inconsistent indentation from the rest, which happens a lot in docstrings.
137
138 :Examples:
139
140 >>> s = '''
141 ... First line.
142 ... More...
143 ... End'''
144
145 >>> print dedent(s)
146 First line.
147 More...
148 End
149
150 >>> s = '''First line
151 ... More...
152 ... End'''
153
154 >>> print dedent(s)
155 First line
156 More...
157 End
158 """
159 out = [textwrap.dedent(t) for t in txt.split('\n',1)
160 if t and not t.isspace()]
161 return '\n'.join(out)
162
163
164 def comment(strng,indent=''):
165 """return an input string, commented out"""
166 template = indent + '# %s'
167 lines = [template % s for s in strng.splitlines(True)]
168 return ''.join(lines)
169
170
171 def configobj2str(cobj):
172 """Dump a Configobj instance to a string."""
173 outstr = StringIO()
174 cobj.write(outstr)
175 return outstr.getvalue()
176
177 def get_config_filename(conf):
178 """Find the filename attribute of a ConfigObj given a sub-section object.
179 """
180 depth = conf.depth
181 for d in range(depth):
182 conf = conf.parent
183 return conf.filename
184
185 def sconf2File(sconf,fname,force=False):
186 """Write a SConfig instance to a given filename.
187
188 :Keywords:
189
190 force : bool (False)
191 If true, force writing even if the file exists.
192 """
193
194 if os.path.isfile(fname) and not force:
195 raise IOError("File %s already exists, use force=True to overwrite" %
196 fname)
197
198 txt = repr(sconf)
199
200 fobj = open(fname,'w')
201 fobj.write(txt)
202 fobj.close()
203
204 def filter_scalars(sc):
205 """ input sc MUST be sorted!!!"""
206 scalars = []
207 maxi = len(sc)-1
208 i = 0
209 while i<len(sc):
210 t = sc[i]
211 if t.startswith('_sconf_'):
212 # Skip altogether private _sconf_ attributes, so we actually issue
213 # a 'continue' call to avoid the append(t) below
214 i += 1
215 continue
216 scalars.append(t)
217 i += 1
218
219 return scalars
220
221
222 def get_scalars(obj):
223 """Return scalars for a Sconf class object"""
224
225 skip = set(['trait_added','trait_modified'])
226 sc = [k for k in obj.__dict__ if not k.startswith('_')]
227 sc.sort()
228 return filter_scalars(sc)
229
230
231 def get_sections(obj,sectionClass):
232 """Return sections for a Sconf class object"""
233 return [(n,v) for (n,v) in obj.__dict__.iteritems()
234 if isclass(v) and issubclass(v,sectionClass)]
235
236
237 def get_instance_sections(inst):
238 """Return sections for a Sconf instance"""
239 sections = [(k,v) for k,v in inst.__dict__.iteritems()
240 if isinstance(v,SConfig) and not k=='_sconf_parent']
241 # Sort the sections by name
242 sections.sort(key=lambda x:x[0])
243 return sections
244
245
246 def partition_instance(obj):
247 """Return scalars,sections for a given Sconf instance.
248 """
249 scnames = []
250 sections = []
251 for k,v in obj.__dict__.iteritems():
252 if isinstance(v,SConfig):
253 if not k=='_sconf_parent':
254 sections.append((k,v))
255 else:
256 scnames.append(k)
257
258 # Sort the sections by name
259 sections.sort(key=lambda x:x[0])
260
261 # Sort the scalar names, filter them and then extract the actual objects
262 scnames.sort()
263 scnames = filter_scalars(scnames)
264 scalars = [(s,obj.__dict__[s]) for s in scnames]
265
266 return scalars, sections
267
268
269 def mk_ConfigObj(filename,mk_missing_file=True):
270 """Return a ConfigObj instance with our hardcoded conventions.
271
272 Use a simple factory that wraps our option choices for using ConfigObj.
273 I'm hard-wiring certain choices here, so we'll always use instances with
274 THESE choices.
275
276 :Parameters:
277
278 filename : string
279 File to read from.
280
281 :Keywords:
282 makeMissingFile : bool (True)
283 If true, the file named by `filename` may not yet exist and it will be
284 automatically created (empty). Else, if `filename` doesn't exist, an
285 IOError will be raised.
286 """
287
288 if mk_missing_file:
289 create_empty = True
290 file_error = False
291 else:
292 create_empty = False
293 file_error = True
294
295 return configobj.ConfigObj(filename,
296 create_empty=create_empty,
297 file_error=file_error,
298 indent_type=' ',
299 interpolation='Template',
300 unrepr=True)
301
302 nullConf = mk_ConfigObj(None)
303
304
305 class RecursiveConfigObj(object):
306 """Object-oriented interface for recursive ConfigObj constructions."""
307
308 def __init__(self,filename):
309 """Return a ConfigObj instance with our hardcoded conventions.
310
311 Use a simple factory that wraps our option choices for using ConfigObj.
312 I'm hard-wiring certain choices here, so we'll always use instances with
313 THESE choices.
314
315 :Parameters:
316
317 filename : string
318 File to read from.
319 """
320
321 self.comp = []
322 self.conf = self._load(filename)
323
324 def _load(self,filename,mk_missing_file=True):
325 conf = mk_ConfigObj(filename,mk_missing_file)
326
327 # Do recursive loading. We only allow (or at least honor) the include
328 # tag at the top-level. For now, we drop the inclusion information so
329 # that there are no restrictions on which levels of the SConfig
330 # hierarchy can use include statements. But this means that
331
332 # if bookkeeping of each separate component of the recursive
333 # construction was requested, make a separate object for storage
334 # there, since we don't want that to be modified by the inclusion
335 # process.
336 self.comp.append(mk_ConfigObj(filename,mk_missing_file))
337
338 incfname = conf.pop('include',None)
339 if incfname is not None:
340 # Do recursive load. We don't want user includes that point to
341 # missing files to fail silently, so in the recursion we disable
342 # auto-creation of missing files.
343 confinc = self._load(incfname,mk_missing_file=False)
344
345 # Update with self to get proper ordering (included files provide
346 # base data, current one overwrites)
347 confinc.update(conf)
348 # And do swap to return the updated structure
349 conf = confinc
350 # Set the filename to be the original file instead of the included
351 # one
352 conf.filename = filename
353 return conf
354
355 ############################################################################
356 # Main SConfig class and supporting exceptions
357 ############################################################################
358
359 class SConfigError(Exception): pass
360
361 class SConfigInvalidKeyError(SConfigError): pass
362
363 class SConfig(object):
364 """A class representing configuration objects.
365
366 Note: this class should NOT have any traits itself, since the actual traits
367 will be declared by subclasses. This class is meant to ONLY declare the
368 necessary initialization/validation methods. """
369
370 # Any traits declared here are prefixed with _sconf_ so that our special
371 # formatting/analysis utilities can distinguish them from user traits and
372 # can avoid them.
373
374 # Once created, the tree's hierarchy can NOT be modified
375 _sconf_parent = None
376
377 def __init__(self,config=None,parent=None,monitor=None):
378 """Makes an SConfig object out of a ConfigObj instance
379 """
380
381 if config is None:
382 config = mk_ConfigObj(None)
383
384 # Validate the set of scalars ...
385 my_scalars = set(get_scalars(self))
386 cf_scalars = set(config.scalars)
387 invalid_scalars = cf_scalars - my_scalars
388 if invalid_scalars:
389 config_fname = get_config_filename(config)
390 m=("In config defined in file: %r\n"
391 "Error processing section: %s\n"
392 "These keys are invalid : %s\n"
393 "Valid key names : %s\n"
394 % (config_fname,self.__class__.__name__,
395 list(invalid_scalars),list(my_scalars)))
396 raise SConfigInvalidKeyError(m)
397
398 # ... and sections
399 section_items = get_sections(self.__class__,SConfig)
400 my_sections = set([n for n,v in section_items])
401 cf_sections = set(config.sections)
402 invalid_sections = cf_sections - my_sections
403 if invalid_sections:
404 config_fname = get_config_filename(config)
405 m = ("In config defined in file: %r\n"
406 "Error processing section: %s\n"
407 "These subsections are invalid : %s\n"
408 "Valid subsection names : %s\n"
409 % (config_fname,self.__class__.__name__,
410 list(invalid_sections),list(my_sections)))
411 raise SConfigInvalidKeyError(m)
412
413 self._sconf_parent = parent
414
415 # Now set the traits based on the config
416 for k in my_scalars:
417 setattr(self,k,config[k])
418
419 # And build subsections
420 for s,v in section_items:
421 sec_config = config.setdefault(s,{})
422 section = v(sec_config,self,monitor=monitor)
423
424 # We must use add_trait instead of setattr because we inherit from
425 # HasStrictTraits, but we need to then do a 'dummy' getattr call on
426 # self so the class trait propagates to the instance.
427 self.add_trait(s,section)
428 getattr(self,s)
429
430 def __repr__(self,depth=0):
431 """Dump a section to a string."""
432
433 indent = ' '*(depth)
434
435 top_name = self.__class__.__name__
436
437 if depth == 0:
438 label = '# %s - plaintext (in .conf format)\n' % top_name
439 else:
440 # Section titles are indented one level less than their contents in
441 # the ConfigObj write methods.
442 sec_indent = ' '*(depth-1)
443 label = '\n'+sec_indent+('[' * depth) + top_name + (']'*depth)
444
445 out = [label]
446
447 doc = self.__class__.__doc__
448 if doc is not None:
449 out.append(comment(dedent(doc),indent))
450
451 scalars, sections = partition_instance(self)
452
453 for s,v in scalars:
454 try:
455 info = self.__base_traits__[s].handler.info()
456 # Get a short version of info with lines of max. 78 chars, so
457 # that after commenting them out (with '# ') they are at most
458 # 80-chars long.
459 out.append(comment(wrap('',info.replace('\n', ' '),78-len(indent)),indent))
460 except (KeyError,AttributeError):
461 pass
462 out.append(indent+('%s = %r' % (s,v)))
463
464 for sname,sec in sections:
465 out.append(sec.__repr__(depth+1))
466
467 return '\n'.join(out)
468
469 def __str__(self):
470 return self.__class__.__name__
471
472
473 ##############################################################################
474 # High-level class(es) and utilities for handling a coupled pair of SConfig and
475 # ConfigObj instances.
476 ##############################################################################
477
478 def path_to_root(obj):
479 """Find the path to the root of a nested SConfig instance."""
480 ob = obj
481 path = []
482 while ob._sconf_parent is not None:
483 path.append(ob.__class__.__name__)
484 ob = ob._sconf_parent
485 path.reverse()
486 return path
487
488
489 def set_value(fconf,path,key,value):
490 """Set a value on a ConfigObj instance, arbitrarily deep."""
491 section = fconf
492 for sname in path:
493 section = section.setdefault(sname,{})
494 section[key] = value
495
496
497 def fmonitor(fconf):
498 """Make a monitor for coupling SConfig instances to ConfigObj ones.
499
500 We must use a closure because Traits makes assumptions about the functions
501 used with on_trait_change() that prevent the use of a callable instance.
502 """
503
504 def mon(obj,name,new):
505 #print 'OBJ:',obj # dbg
506 #print 'NAM:',name # dbg
507 #print 'NEW:',new # dbg
508 set_value(fconf,path_to_root(obj),name,new)
509
510 return mon
511
512
513 class SConfigManager(object):
514 """A simple object to manage and sync a SConfig and a ConfigObj pair.
515 """
516
517 def __init__(self,configClass,configFilename,filePriority=True):
518 """Make a new SConfigManager.
519
520 :Parameters:
521
522 configClass : class
523
524 configFilename : string
525 If the filename points to a non-existent file, it will be created
526 empty. This is useful when creating a file form from an existing
527 configClass with the class defaults.
528
529
530 :Keywords:
531
532 filePriority : bool (True)
533
534 If true, at construction time the file object takes priority and
535 overwrites the contents of the config object. Else, the data flow
536 is reversed and the file object will be overwritten with the
537 configClass defaults at write() time.
538 """
539
540 rconf = RecursiveConfigObj(configFilename)
541 # In a hierarchical object, the two following fconfs are *very*
542 # different. In self.fconf, we'll keep the outer-most fconf associated
543 # directly to the original filename. self.fconf_combined, instead,
544 # contains an object which has the combined effect of having merged all
545 # the called files in the recursive chain.
546 self.fconf = rconf.comp[0]
547 self.fconf_combined = rconf.conf
548
549 # Create a monitor to track and apply trait changes to the sconf
550 # instance over into the fconf one
551 monitor = fmonitor(self.fconf)
552
553 if filePriority:
554 self.sconf = configClass(self.fconf_combined,monitor=monitor)
555 else:
556 # Push defaults onto file object
557 self.sconf = configClass(mk_ConfigObj(None),monitor=monitor)
558 self.fconfUpdate(self.fconf,self.sconf)
559
560 def fconfUpdate(self,fconf,sconf):
561 """Update the fconf object with the data from sconf"""
562
563 scalars, sections = partition_instance(sconf)
564
565 for s,v in scalars:
566 fconf[s] = v
567
568 for secname,sec in sections:
569 self.fconfUpdate(fconf.setdefault(secname,{}),sec)
570
571 def write(self,filename=None):
572 """Write out to disk.
573
574 This method writes out only to the top file in a hierarchical
575 configuration, which means that the class defaults and other values not
576 explicitly set in the top level file are NOT written out.
577
578 :Keywords:
579
580 filename : string (None)
581 If given, the output is written to this file, otherwise the
582 .filename attribute of the top-level configuration object is used.
583 """
584 if filename is not None:
585 file_obj = open(filename,'w')
586 out = self.fconf.write(file_obj)
587 file_obj.close()
588 return out
589 else:
590 return self.fconf.write()
591
592 def writeAll(self,filename=None):
593 """Write out the entire configuration to disk.
594
595 This method, in contrast with write(), updates the .fconf_combined
596 object with the *entire* .sconf instance, and then writes it out to
597 disk. This method is thus useful for generating files that have a
598 self-contained, non-hierarchical file.
599
600 :Keywords:
601
602 filename : string (None)
603 If given, the output is written to this file, otherwise the
604 .filename attribute of the top-level configuration object is used.
605 """
606 if filename is not None:
607 file_obj = open(filename,'w')
608 self.fconfUpdate(self.fconf_combined,self.sconf)
609 out = self.fconf_combined.write(file_obj)
610 file_obj.close()
611 return out
612 else:
613 self.fconfUpdate(self.fconf_combined,self.sconf)
614 return self.fconf_combined.write()
615
616 def sconf_str(self):
617 return str(self.sconf)
618
619 def fconf_str(self):
620 return configobj2str(self.fconf)
621
622 __repr__ = __str__ = fconf_str
@@ -0,0 +1,37 b''
1 """Little utilities for testing tconfig.
2
3 This module is meant to be used via
4
5 import sctst; reload(sctst)
6 from sctst import *
7
8 at the top of the actual test scripts, so that they all get the entire set of
9 common test tools with minimal fuss.
10 """
11
12 # Standard library imports
13 import os
14 import sys
15 from pprint import pprint
16
17 # Our own imports.
18
19 from IPython.config import sconfig
20 reload(sconfig)
21
22 from sconfig import mkConfigObj, RecursiveConfigObj, SConfigManager, \
23 sconf2file
24
25 # Simple utilities/classes for testing
26
27 def cat(fname):
28 print '### FILENAME:',fname
29 print open(fname).read()
30
31
32 class App(object):
33 """A trivial 'application' class to be initialized.
34 """
35 def __init__(self,config_class,config_filename):
36 self.rcman = SConfigManager(config_class,config_filename)
37 self.rc = self.rcman.sconf
@@ -0,0 +1,14 b''
1 # Toy example of a TConfig-based configuration description
2
3 # This is the class declaration for the configuration:
4
5 # SimpleConfig
6 # Configuration for my application
7
8 solver = "Iterative2"
9
10 [Protocol]
11 # Specify the Protocol
12
13 ptype = "http2"
14 max_users = 4
@@ -0,0 +1,14 b''
1 # Toy example of a TConfig-based configuration description
2
3 # This is the class declaration for the configuration:
4
5 # SimpleConfig
6 # Configuration for my application
7
8 datafile = string(default='data.txt')
9 solver = option('Direct','Iterative')
10
11 [Protocol]
12 # Specify the Protocol
13 ptype = option('http','ftp','ssh')
14 max_users = integer(1,10)
@@ -0,0 +1,59 b''
1 """Toy example of reading an SConf object."""
2
3 from IPython.external.configobj import ConfigObj
4 from IPython.external import configobj, validate
5
6
7 from IPython.config import sconfig
8 reload(sconfig)
9
10 configspecfilename = 'simple.spec.conf'
11 filename = 'simple.conf'
12
13 print '*'*80
14 configspec = ConfigObj(configspecfilename, encoding='UTF8',
15 list_values=False)
16 print sconfig.configobj2str(configspec)
17
18 print '*'*80
19 config = ConfigObj(filename, configspec=configspec,
20 interpolation='Template',
21 unrepr=True)
22 print sconfig.configobj2str(config)
23 vdt = validate.Validator()
24 test = config.validate(vdt,preserve_errors=True)
25
26 ####
27 vdt = validate.Validator()
28 class Bunch: pass
29 vf = Bunch()
30 vf.__dict__.update(vdt.functions)
31 vf.pass_ = vdt.functions['pass']
32 vf.__dict__.pop('',None)
33 vf.__dict__.pop('pass',None)
34 ###
35
36
37 if test==True:
38 print 'All OK'
39 else:
40 err = configobj.flatten_errors(config,test)
41 print 'Flat errors:'
42 for secs,key,result in err:
43 if secs == []:
44 print 'DEFAULT:','key:',key,'err:',result
45 else:
46 print 'Secs:',secs,'key:',key,'err:',result
47
48
49 ##
50 print '*'*80
51
52 sc = sconfig.SConfig(configspecfilename)
53
54
55
56 ####
57
58
59
@@ -0,0 +1,278 b''
1 # -*- coding: utf-8 -*-
2 """String interpolation for Python (by Ka-Ping Yee, 14 Feb 2000).
3
4 This module lets you quickly and conveniently interpolate values into
5 strings (in the flavour of Perl or Tcl, but with less extraneous
6 punctuation). You get a bit more power than in the other languages,
7 because this module allows subscripting, slicing, function calls,
8 attribute lookup, or arbitrary expressions. Variables and expressions
9 are evaluated in the namespace of the caller.
10
11 The itpl() function returns the result of interpolating a string, and
12 printpl() prints out an interpolated string. Here are some examples:
13
14 from Itpl import printpl
15 printpl("Here is a $string.")
16 printpl("Here is a $module.member.")
17 printpl("Here is an $object.member.")
18 printpl("Here is a $functioncall(with, arguments).")
19 printpl("Here is an ${arbitrary + expression}.")
20 printpl("Here is an $array[3] member.")
21 printpl("Here is a $dictionary['member'].")
22
23 The filter() function filters a file object so that output through it
24 is interpolated. This lets you produce the illusion that Python knows
25 how to do interpolation:
26
27 import Itpl
28 sys.stdout = Itpl.filter()
29 f = "fancy"
30 print "Isn't this $f?"
31 print "Standard output has been replaced with a $sys.stdout object."
32 sys.stdout = Itpl.unfilter()
33 print "Okay, back $to $normal."
34
35 Under the hood, the Itpl class represents a string that knows how to
36 interpolate values. An instance of the class parses the string once
37 upon initialization; the evaluation and substitution can then be done
38 each time the instance is evaluated with str(instance). For example:
39
40 from Itpl import Itpl
41 s = Itpl("Here is $foo.")
42 foo = 5
43 print str(s)
44 foo = "bar"
45 print str(s)
46
47 $Id: Itpl.py 2305 2007-05-04 05:34:42Z bgranger $
48 """ # ' -> close an open quote for stupid emacs
49
50 #*****************************************************************************
51 #
52 # Copyright (c) 2001 Ka-Ping Yee <ping@lfw.org>
53 #
54 #
55 # Published under the terms of the MIT license, hereby reproduced:
56 #
57 # Permission is hereby granted, free of charge, to any person obtaining a copy
58 # of this software and associated documentation files (the "Software"), to
59 # deal in the Software without restriction, including without limitation the
60 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
61 # sell copies of the Software, and to permit persons to whom the Software is
62 # furnished to do so, subject to the following conditions:
63 #
64 # The above copyright notice and this permission notice shall be included in
65 # all copies or substantial portions of the Software.
66 #
67 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
72 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
73 # IN THE SOFTWARE.
74 #
75 #*****************************************************************************
76
77 __author__ = 'Ka-Ping Yee <ping@lfw.org>'
78 __license__ = 'MIT'
79
80 import string
81 import sys
82 from tokenize import tokenprog
83 from types import StringType
84
85 class ItplError(ValueError):
86 def __init__(self, text, pos):
87 self.text = text
88 self.pos = pos
89 def __str__(self):
90 return "unfinished expression in %s at char %d" % (
91 repr(self.text), self.pos)
92
93 def matchorfail(text, pos):
94 match = tokenprog.match(text, pos)
95 if match is None:
96 raise ItplError(text, pos)
97 return match, match.end()
98
99 class Itpl:
100 """Class representing a string with interpolation abilities.
101
102 Upon creation, an instance works out what parts of the format
103 string are literal and what parts need to be evaluated. The
104 evaluation and substitution happens in the namespace of the
105 caller when str(instance) is called."""
106
107 def __init__(self, format,codec='utf_8',encoding_errors='backslashreplace'):
108 """The single mandatory argument to this constructor is a format
109 string.
110
111 The format string is parsed according to the following rules:
112
113 1. A dollar sign and a name, possibly followed by any of:
114 - an open-paren, and anything up to the matching paren
115 - an open-bracket, and anything up to the matching bracket
116 - a period and a name
117 any number of times, is evaluated as a Python expression.
118
119 2. A dollar sign immediately followed by an open-brace, and
120 anything up to the matching close-brace, is evaluated as
121 a Python expression.
122
123 3. Outside of the expressions described in the above two rules,
124 two dollar signs in a row give you one literal dollar sign.
125
126 Optional arguments:
127
128 - codec('utf_8'): a string containing the name of a valid Python
129 codec.
130
131 - encoding_errors('backslashreplace'): a string with a valid error handling
132 policy. See the codecs module documentation for details.
133
134 These are used to encode the format string if a call to str() fails on
135 the expanded result."""
136
137 if not isinstance(format,basestring):
138 raise TypeError, "needs string initializer"
139 self.format = format
140 self.codec = codec
141 self.encoding_errors = encoding_errors
142
143 namechars = "abcdefghijklmnopqrstuvwxyz" \
144 "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
145 chunks = []
146 pos = 0
147
148 while 1:
149 dollar = string.find(format, "$", pos)
150 if dollar < 0: break
151 nextchar = format[dollar+1]
152
153 if nextchar == "{":
154 chunks.append((0, format[pos:dollar]))
155 pos, level = dollar+2, 1
156 while level:
157 match, pos = matchorfail(format, pos)
158 tstart, tend = match.regs[3]
159 token = format[tstart:tend]
160 if token == "{": level = level+1
161 elif token == "}": level = level-1
162 chunks.append((1, format[dollar+2:pos-1]))
163
164 elif nextchar in namechars:
165 chunks.append((0, format[pos:dollar]))
166 match, pos = matchorfail(format, dollar+1)
167 while pos < len(format):
168 if format[pos] == "." and \
169 pos+1 < len(format) and format[pos+1] in namechars:
170 match, pos = matchorfail(format, pos+1)
171 elif format[pos] in "([":
172 pos, level = pos+1, 1
173 while level:
174 match, pos = matchorfail(format, pos)
175 tstart, tend = match.regs[3]
176 token = format[tstart:tend]
177 if token[0] in "([": level = level+1
178 elif token[0] in ")]": level = level-1
179 else: break
180 chunks.append((1, format[dollar+1:pos]))
181
182 else:
183 chunks.append((0, format[pos:dollar+1]))
184 pos = dollar + 1 + (nextchar == "$")
185
186 if pos < len(format): chunks.append((0, format[pos:]))
187 self.chunks = chunks
188
189 def __repr__(self):
190 return "<Itpl %s >" % repr(self.format)
191
192 def _str(self,glob,loc):
193 """Evaluate to a string in the given globals/locals.
194
195 The final output is built by calling str(), but if this fails, the
196 result is encoded with the instance's codec and error handling policy,
197 via a call to out.encode(self.codec,self.encoding_errors)"""
198 result = []
199 app = result.append
200 for live, chunk in self.chunks:
201 if live: app(str(eval(chunk,glob,loc)))
202 else: app(chunk)
203 out = ''.join(result)
204 try:
205 return str(out)
206 except UnicodeError:
207 return out.encode(self.codec,self.encoding_errors)
208
209 def __str__(self):
210 """Evaluate and substitute the appropriate parts of the string."""
211
212 # We need to skip enough frames to get to the actual caller outside of
213 # Itpl.
214 frame = sys._getframe(1)
215 while frame.f_globals["__name__"] == __name__: frame = frame.f_back
216 loc, glob = frame.f_locals, frame.f_globals
217
218 return self._str(glob,loc)
219
220 class ItplNS(Itpl):
221 """Class representing a string with interpolation abilities.
222
223 This inherits from Itpl, but at creation time a namespace is provided
224 where the evaluation will occur. The interpolation becomes a bit more
225 efficient, as no traceback needs to be extracte. It also allows the
226 caller to supply a different namespace for the interpolation to occur than
227 its own."""
228
229 def __init__(self, format,globals,locals=None,
230 codec='utf_8',encoding_errors='backslashreplace'):
231 """ItplNS(format,globals[,locals]) -> interpolating string instance.
232
233 This constructor, besides a format string, takes a globals dictionary
234 and optionally a locals (which defaults to globals if not provided).
235
236 For further details, see the Itpl constructor."""
237
238 if locals is None:
239 locals = globals
240 self.globals = globals
241 self.locals = locals
242 Itpl.__init__(self,format,codec,encoding_errors)
243
244 def __str__(self):
245 """Evaluate and substitute the appropriate parts of the string."""
246 return self._str(self.globals,self.locals)
247
248 def __repr__(self):
249 return "<ItplNS %s >" % repr(self.format)
250
251 # utilities for fast printing
252 def itpl(text): return str(Itpl(text))
253 def printpl(text): print itpl(text)
254 # versions with namespace
255 def itplns(text,globals,locals=None): return str(ItplNS(text,globals,locals))
256 def printplns(text,globals,locals=None): print itplns(text,globals,locals)
257
258 class ItplFile:
259 """A file object that filters each write() through an interpolator."""
260 def __init__(self, file): self.file = file
261 def __repr__(self): return "<interpolated " + repr(self.file) + ">"
262 def __getattr__(self, attr): return getattr(self.file, attr)
263 def write(self, text): self.file.write(str(Itpl(text)))
264
265 def filter(file=sys.stdout):
266 """Return an ItplFile that filters writes to the given file object.
267
268 'file = filter(file)' replaces 'file' with a filtered object that
269 has a write() method. When called with no argument, this creates
270 a filter to sys.stdout."""
271 return ItplFile(file)
272
273 def unfilter(ifile=None):
274 """Return the original file that corresponds to the given ItplFile.
275
276 'file = unfilter(file)' undoes the effect of 'file = filter(file)'.
277 'sys.stdout = unfilter()' undoes the effect of 'sys.stdout = filter()'."""
278 return ifile and ifile.file or sys.stdout.file
This diff has been collapsed as it changes many lines, (2501 lines changed) Show them Hide them
@@ -0,0 +1,2501 b''
1 # configobj.py
2 # A config file reader/writer that supports nested sections in config files.
3 # Copyright (C) 2005-2008 Michael Foord, Nicola Larosa
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
5 # nico AT tekNico DOT net
6
7 # ConfigObj 4
8 # http://www.voidspace.org.uk/python/configobj.html
9
10 # Released subject to the BSD License
11 # Please see http://www.voidspace.org.uk/python/license.shtml
12
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14 # For information about bugfixes, updates and support, please join the
15 # ConfigObj mailing list:
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
17 # Comments, suggestions and bug reports welcome.
18
19 from __future__ import generators
20
21 import sys
22 INTP_VER = sys.version_info[:2]
23 if INTP_VER < (2, 2):
24 raise RuntimeError("Python v.2.2 or later needed")
25
26 import os, re
27 compiler = None
28 try:
29 import compiler
30 except ImportError:
31 # for IronPython
32 pass
33 from types import StringTypes
34 from warnings import warn
35 try:
36 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
37 except ImportError:
38 # Python 2.2 does not have these
39 # UTF-8
40 BOM_UTF8 = '\xef\xbb\xbf'
41 # UTF-16, little endian
42 BOM_UTF16_LE = '\xff\xfe'
43 # UTF-16, big endian
44 BOM_UTF16_BE = '\xfe\xff'
45 if sys.byteorder == 'little':
46 # UTF-16, native endianness
47 BOM_UTF16 = BOM_UTF16_LE
48 else:
49 # UTF-16, native endianness
50 BOM_UTF16 = BOM_UTF16_BE
51
52 # A dictionary mapping BOM to
53 # the encoding to decode with, and what to set the
54 # encoding attribute to.
55 BOMS = {
56 BOM_UTF8: ('utf_8', None),
57 BOM_UTF16_BE: ('utf16_be', 'utf_16'),
58 BOM_UTF16_LE: ('utf16_le', 'utf_16'),
59 BOM_UTF16: ('utf_16', 'utf_16'),
60 }
61 # All legal variants of the BOM codecs.
62 # TODO: the list of aliases is not meant to be exhaustive, is there a
63 # better way ?
64 BOM_LIST = {
65 'utf_16': 'utf_16',
66 'u16': 'utf_16',
67 'utf16': 'utf_16',
68 'utf-16': 'utf_16',
69 'utf16_be': 'utf16_be',
70 'utf_16_be': 'utf16_be',
71 'utf-16be': 'utf16_be',
72 'utf16_le': 'utf16_le',
73 'utf_16_le': 'utf16_le',
74 'utf-16le': 'utf16_le',
75 'utf_8': 'utf_8',
76 'u8': 'utf_8',
77 'utf': 'utf_8',
78 'utf8': 'utf_8',
79 'utf-8': 'utf_8',
80 }
81
82 # Map of encodings to the BOM to write.
83 BOM_SET = {
84 'utf_8': BOM_UTF8,
85 'utf_16': BOM_UTF16,
86 'utf16_be': BOM_UTF16_BE,
87 'utf16_le': BOM_UTF16_LE,
88 None: BOM_UTF8
89 }
90
91
92 def match_utf8(encoding):
93 return BOM_LIST.get(encoding.lower()) == 'utf_8'
94
95
96 # Quote strings used for writing values
97 squot = "'%s'"
98 dquot = '"%s"'
99 noquot = "%s"
100 wspace_plus = ' \r\t\n\v\t\'"'
101 tsquot = '"""%s"""'
102 tdquot = "'''%s'''"
103
104 try:
105 enumerate
106 except NameError:
107 def enumerate(obj):
108 """enumerate for Python 2.2."""
109 i = -1
110 for item in obj:
111 i += 1
112 yield i, item
113
114 try:
115 True, False
116 except NameError:
117 True, False = 1, 0
118
119
120 __version__ = '4.5.2'
121
122 __revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $'
123
124 __docformat__ = "restructuredtext en"
125
126 __all__ = (
127 '__version__',
128 'DEFAULT_INDENT_TYPE',
129 'DEFAULT_INTERPOLATION',
130 'ConfigObjError',
131 'NestingError',
132 'ParseError',
133 'DuplicateError',
134 'ConfigspecError',
135 'ConfigObj',
136 'SimpleVal',
137 'InterpolationError',
138 'InterpolationLoopError',
139 'MissingInterpolationOption',
140 'RepeatSectionError',
141 'ReloadError',
142 'UnreprError',
143 'UnknownType',
144 '__docformat__',
145 'flatten_errors',
146 )
147
148 DEFAULT_INTERPOLATION = 'configparser'
149 DEFAULT_INDENT_TYPE = ' '
150 MAX_INTERPOL_DEPTH = 10
151
152 OPTION_DEFAULTS = {
153 'interpolation': True,
154 'raise_errors': False,
155 'list_values': True,
156 'create_empty': False,
157 'file_error': False,
158 'configspec': None,
159 'stringify': True,
160 # option may be set to one of ('', ' ', '\t')
161 'indent_type': None,
162 'encoding': None,
163 'default_encoding': None,
164 'unrepr': False,
165 'write_empty_values': False,
166 }
167
168
169
170 def getObj(s):
171 s = "a=" + s
172 if compiler is None:
173 raise ImportError('compiler module not available')
174 p = compiler.parse(s)
175 return p.getChildren()[1].getChildren()[0].getChildren()[1]
176
177
178 class UnknownType(Exception):
179 pass
180
181
182 class Builder(object):
183
184 def build(self, o):
185 m = getattr(self, 'build_' + o.__class__.__name__, None)
186 if m is None:
187 raise UnknownType(o.__class__.__name__)
188 return m(o)
189
190 def build_List(self, o):
191 return map(self.build, o.getChildren())
192
193 def build_Const(self, o):
194 return o.value
195
196 def build_Dict(self, o):
197 d = {}
198 i = iter(map(self.build, o.getChildren()))
199 for el in i:
200 d[el] = i.next()
201 return d
202
203 def build_Tuple(self, o):
204 return tuple(self.build_List(o))
205
206 def build_Name(self, o):
207 if o.name == 'None':
208 return None
209 if o.name == 'True':
210 return True
211 if o.name == 'False':
212 return False
213
214 # An undefined Name
215 raise UnknownType('Undefined Name')
216
217 def build_Add(self, o):
218 real, imag = map(self.build_Const, o.getChildren())
219 try:
220 real = float(real)
221 except TypeError:
222 raise UnknownType('Add')
223 if not isinstance(imag, complex) or imag.real != 0.0:
224 raise UnknownType('Add')
225 return real+imag
226
227 def build_Getattr(self, o):
228 parent = self.build(o.expr)
229 return getattr(parent, o.attrname)
230
231 def build_UnarySub(self, o):
232 return -self.build_Const(o.getChildren()[0])
233
234 def build_UnaryAdd(self, o):
235 return self.build_Const(o.getChildren()[0])
236
237
238 _builder = Builder()
239
240
241 def unrepr(s):
242 if not s:
243 return s
244 return _builder.build(getObj(s))
245
246
247
248 class ConfigObjError(SyntaxError):
249 """
250 This is the base class for all errors that ConfigObj raises.
251 It is a subclass of SyntaxError.
252 """
253 def __init__(self, message='', line_number=None, line=''):
254 self.line = line
255 self.line_number = line_number
256 self.message = message
257 SyntaxError.__init__(self, message)
258
259
260 class NestingError(ConfigObjError):
261 """
262 This error indicates a level of nesting that doesn't match.
263 """
264
265
266 class ParseError(ConfigObjError):
267 """
268 This error indicates that a line is badly written.
269 It is neither a valid ``key = value`` line,
270 nor a valid section marker line.
271 """
272
273
274 class ReloadError(IOError):
275 """
276 A 'reload' operation failed.
277 This exception is a subclass of ``IOError``.
278 """
279 def __init__(self):
280 IOError.__init__(self, 'reload failed, filename is not set.')
281
282
283 class DuplicateError(ConfigObjError):
284 """
285 The keyword or section specified already exists.
286 """
287
288
289 class ConfigspecError(ConfigObjError):
290 """
291 An error occured whilst parsing a configspec.
292 """
293
294
295 class InterpolationError(ConfigObjError):
296 """Base class for the two interpolation errors."""
297
298
299 class InterpolationLoopError(InterpolationError):
300 """Maximum interpolation depth exceeded in string interpolation."""
301
302 def __init__(self, option):
303 InterpolationError.__init__(
304 self,
305 'interpolation loop detected in value "%s".' % option)
306
307
308 class RepeatSectionError(ConfigObjError):
309 """
310 This error indicates additional sections in a section with a
311 ``__many__`` (repeated) section.
312 """
313
314
315 class MissingInterpolationOption(InterpolationError):
316 """A value specified for interpolation was missing."""
317
318 def __init__(self, option):
319 InterpolationError.__init__(
320 self,
321 'missing option "%s" in interpolation.' % option)
322
323
324 class UnreprError(ConfigObjError):
325 """An error parsing in unrepr mode."""
326
327
328
329 class InterpolationEngine(object):
330 """
331 A helper class to help perform string interpolation.
332
333 This class is an abstract base class; its descendants perform
334 the actual work.
335 """
336
337 # compiled regexp to use in self.interpolate()
338 _KEYCRE = re.compile(r"%\(([^)]*)\)s")
339
340 def __init__(self, section):
341 # the Section instance that "owns" this engine
342 self.section = section
343
344
345 def interpolate(self, key, value):
346 def recursive_interpolate(key, value, section, backtrail):
347 """The function that does the actual work.
348
349 ``value``: the string we're trying to interpolate.
350 ``section``: the section in which that string was found
351 ``backtrail``: a dict to keep track of where we've been,
352 to detect and prevent infinite recursion loops
353
354 This is similar to a depth-first-search algorithm.
355 """
356 # Have we been here already?
357 if backtrail.has_key((key, section.name)):
358 # Yes - infinite loop detected
359 raise InterpolationLoopError(key)
360 # Place a marker on our backtrail so we won't come back here again
361 backtrail[(key, section.name)] = 1
362
363 # Now start the actual work
364 match = self._KEYCRE.search(value)
365 while match:
366 # The actual parsing of the match is implementation-dependent,
367 # so delegate to our helper function
368 k, v, s = self._parse_match(match)
369 if k is None:
370 # That's the signal that no further interpolation is needed
371 replacement = v
372 else:
373 # Further interpolation may be needed to obtain final value
374 replacement = recursive_interpolate(k, v, s, backtrail)
375 # Replace the matched string with its final value
376 start, end = match.span()
377 value = ''.join((value[:start], replacement, value[end:]))
378 new_search_start = start + len(replacement)
379 # Pick up the next interpolation key, if any, for next time
380 # through the while loop
381 match = self._KEYCRE.search(value, new_search_start)
382
383 # Now safe to come back here again; remove marker from backtrail
384 del backtrail[(key, section.name)]
385
386 return value
387
388 # Back in interpolate(), all we have to do is kick off the recursive
389 # function with appropriate starting values
390 value = recursive_interpolate(key, value, self.section, {})
391 return value
392
393
394 def _fetch(self, key):
395 """Helper function to fetch values from owning section.
396
397 Returns a 2-tuple: the value, and the section where it was found.
398 """
399 # switch off interpolation before we try and fetch anything !
400 save_interp = self.section.main.interpolation
401 self.section.main.interpolation = False
402
403 # Start at section that "owns" this InterpolationEngine
404 current_section = self.section
405 while True:
406 # try the current section first
407 val = current_section.get(key)
408 if val is not None:
409 break
410 # try "DEFAULT" next
411 val = current_section.get('DEFAULT', {}).get(key)
412 if val is not None:
413 break
414 # move up to parent and try again
415 # top-level's parent is itself
416 if current_section.parent is current_section:
417 # reached top level, time to give up
418 break
419 current_section = current_section.parent
420
421 # restore interpolation to previous value before returning
422 self.section.main.interpolation = save_interp
423 if val is None:
424 raise MissingInterpolationOption(key)
425 return val, current_section
426
427
428 def _parse_match(self, match):
429 """Implementation-dependent helper function.
430
431 Will be passed a match object corresponding to the interpolation
432 key we just found (e.g., "%(foo)s" or "$foo"). Should look up that
433 key in the appropriate config file section (using the ``_fetch()``
434 helper function) and return a 3-tuple: (key, value, section)
435
436 ``key`` is the name of the key we're looking for
437 ``value`` is the value found for that key
438 ``section`` is a reference to the section where it was found
439
440 ``key`` and ``section`` should be None if no further
441 interpolation should be performed on the resulting value
442 (e.g., if we interpolated "$$" and returned "$").
443 """
444 raise NotImplementedError()
445
446
447
448 class ConfigParserInterpolation(InterpolationEngine):
449 """Behaves like ConfigParser."""
450 _KEYCRE = re.compile(r"%\(([^)]*)\)s")
451
452 def _parse_match(self, match):
453 key = match.group(1)
454 value, section = self._fetch(key)
455 return key, value, section
456
457
458
459 class TemplateInterpolation(InterpolationEngine):
460 """Behaves like string.Template."""
461 _delimiter = '$'
462 _KEYCRE = re.compile(r"""
463 \$(?:
464 (?P<escaped>\$) | # Two $ signs
465 (?P<named>[_a-z][_a-z0-9]*) | # $name format
466 {(?P<braced>[^}]*)} # ${name} format
467 )
468 """, re.IGNORECASE | re.VERBOSE)
469
470 def _parse_match(self, match):
471 # Valid name (in or out of braces): fetch value from section
472 key = match.group('named') or match.group('braced')
473 if key is not None:
474 value, section = self._fetch(key)
475 return key, value, section
476 # Escaped delimiter (e.g., $$): return single delimiter
477 if match.group('escaped') is not None:
478 # Return None for key and section to indicate it's time to stop
479 return None, self._delimiter, None
480 # Anything else: ignore completely, just return it unchanged
481 return None, match.group(), None
482
483
484 interpolation_engines = {
485 'configparser': ConfigParserInterpolation,
486 'template': TemplateInterpolation,
487 }
488
489
490
491 class Section(dict):
492 """
493 A dictionary-like object that represents a section in a config file.
494
495 It does string interpolation if the 'interpolation' attribute
496 of the 'main' object is set to True.
497
498 Interpolation is tried first from this object, then from the 'DEFAULT'
499 section of this object, next from the parent and its 'DEFAULT' section,
500 and so on until the main object is reached.
501
502 A Section will behave like an ordered dictionary - following the
503 order of the ``scalars`` and ``sections`` attributes.
504 You can use this to change the order of members.
505
506 Iteration follows the order: scalars, then sections.
507 """
508
509 def __init__(self, parent, depth, main, indict=None, name=None):
510 """
511 * parent is the section above
512 * depth is the depth level of this section
513 * main is the main ConfigObj
514 * indict is a dictionary to initialise the section with
515 """
516 if indict is None:
517 indict = {}
518 dict.__init__(self)
519 # used for nesting level *and* interpolation
520 self.parent = parent
521 # used for the interpolation attribute
522 self.main = main
523 # level of nesting depth of this Section
524 self.depth = depth
525 # purely for information
526 self.name = name
527 #
528 self._initialise()
529 # we do this explicitly so that __setitem__ is used properly
530 # (rather than just passing to ``dict.__init__``)
531 for entry, value in indict.iteritems():
532 self[entry] = value
533
534
535 def _initialise(self):
536 # the sequence of scalar values in this Section
537 self.scalars = []
538 # the sequence of sections in this Section
539 self.sections = []
540 # for comments :-)
541 self.comments = {}
542 self.inline_comments = {}
543 # for the configspec
544 self.configspec = {}
545 self._order = []
546 self._configspec_comments = {}
547 self._configspec_inline_comments = {}
548 self._cs_section_comments = {}
549 self._cs_section_inline_comments = {}
550 # for defaults
551 self.defaults = []
552 self.default_values = {}
553
554
555 def _interpolate(self, key, value):
556 try:
557 # do we already have an interpolation engine?
558 engine = self._interpolation_engine
559 except AttributeError:
560 # not yet: first time running _interpolate(), so pick the engine
561 name = self.main.interpolation
562 if name == True: # note that "if name:" would be incorrect here
563 # backwards-compatibility: interpolation=True means use default
564 name = DEFAULT_INTERPOLATION
565 name = name.lower() # so that "Template", "template", etc. all work
566 class_ = interpolation_engines.get(name, None)
567 if class_ is None:
568 # invalid value for self.main.interpolation
569 self.main.interpolation = False
570 return value
571 else:
572 # save reference to engine so we don't have to do this again
573 engine = self._interpolation_engine = class_(self)
574 # let the engine do the actual work
575 return engine.interpolate(key, value)
576
577
578 def __getitem__(self, key):
579 """Fetch the item and do string interpolation."""
580 val = dict.__getitem__(self, key)
581 if self.main.interpolation and isinstance(val, StringTypes):
582 return self._interpolate(key, val)
583 return val
584
585
586 def __setitem__(self, key, value, unrepr=False):
587 """
588 Correctly set a value.
589
590 Making dictionary values Section instances.
591 (We have to special case 'Section' instances - which are also dicts)
592
593 Keys must be strings.
594 Values need only be strings (or lists of strings) if
595 ``main.stringify`` is set.
596
597 `unrepr`` must be set when setting a value to a dictionary, without
598 creating a new sub-section.
599 """
600 if not isinstance(key, StringTypes):
601 raise ValueError('The key "%s" is not a string.' % key)
602
603 # add the comment
604 if not self.comments.has_key(key):
605 self.comments[key] = []
606 self.inline_comments[key] = ''
607 # remove the entry from defaults
608 if key in self.defaults:
609 self.defaults.remove(key)
610 #
611 if isinstance(value, Section):
612 if not self.has_key(key):
613 self.sections.append(key)
614 dict.__setitem__(self, key, value)
615 elif isinstance(value, dict) and not unrepr:
616 # First create the new depth level,
617 # then create the section
618 if not self.has_key(key):
619 self.sections.append(key)
620 new_depth = self.depth + 1
621 dict.__setitem__(
622 self,
623 key,
624 Section(
625 self,
626 new_depth,
627 self.main,
628 indict=value,
629 name=key))
630 else:
631 if not self.has_key(key):
632 self.scalars.append(key)
633 if not self.main.stringify:
634 if isinstance(value, StringTypes):
635 pass
636 elif isinstance(value, (list, tuple)):
637 for entry in value:
638 if not isinstance(entry, StringTypes):
639 raise TypeError('Value is not a string "%s".' % entry)
640 else:
641 raise TypeError('Value is not a string "%s".' % value)
642 dict.__setitem__(self, key, value)
643
644
645 def __delitem__(self, key):
646 """Remove items from the sequence when deleting."""
647 dict. __delitem__(self, key)
648 if key in self.scalars:
649 self.scalars.remove(key)
650 else:
651 self.sections.remove(key)
652 del self.comments[key]
653 del self.inline_comments[key]
654
655
656 def get(self, key, default=None):
657 """A version of ``get`` that doesn't bypass string interpolation."""
658 try:
659 return self[key]
660 except KeyError:
661 return default
662
663
664 def update(self, indict):
665 """
666 A version of update that uses our ``__setitem__``.
667 """
668 for entry in indict:
669 self[entry] = indict[entry]
670
671
672 def pop(self, key, *args):
673 """
674 'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
675 If key is not found, d is returned if given, otherwise KeyError is raised'
676 """
677 val = dict.pop(self, key, *args)
678 if key in self.scalars:
679 del self.comments[key]
680 del self.inline_comments[key]
681 self.scalars.remove(key)
682 elif key in self.sections:
683 del self.comments[key]
684 del self.inline_comments[key]
685 self.sections.remove(key)
686 if self.main.interpolation and isinstance(val, StringTypes):
687 return self._interpolate(key, val)
688 return val
689
690
691 def popitem(self):
692 """Pops the first (key,val)"""
693 sequence = (self.scalars + self.sections)
694 if not sequence:
695 raise KeyError(": 'popitem(): dictionary is empty'")
696 key = sequence[0]
697 val = self[key]
698 del self[key]
699 return key, val
700
701
702 def clear(self):
703 """
704 A version of clear that also affects scalars/sections
705 Also clears comments and configspec.
706
707 Leaves other attributes alone :
708 depth/main/parent are not affected
709 """
710 dict.clear(self)
711 self.scalars = []
712 self.sections = []
713 self.comments = {}
714 self.inline_comments = {}
715 self.configspec = {}
716
717
718 def setdefault(self, key, default=None):
719 """A version of setdefault that sets sequence if appropriate."""
720 try:
721 return self[key]
722 except KeyError:
723 self[key] = default
724 return self[key]
725
726
727 def items(self):
728 """D.items() -> list of D's (key, value) pairs, as 2-tuples"""
729 return zip((self.scalars + self.sections), self.values())
730
731
732 def keys(self):
733 """D.keys() -> list of D's keys"""
734 return (self.scalars + self.sections)
735
736
737 def values(self):
738 """D.values() -> list of D's values"""
739 return [self[key] for key in (self.scalars + self.sections)]
740
741
742 def iteritems(self):
743 """D.iteritems() -> an iterator over the (key, value) items of D"""
744 return iter(self.items())
745
746
747 def iterkeys(self):
748 """D.iterkeys() -> an iterator over the keys of D"""
749 return iter((self.scalars + self.sections))
750
751 __iter__ = iterkeys
752
753
754 def itervalues(self):
755 """D.itervalues() -> an iterator over the values of D"""
756 return iter(self.values())
757
758
759 def __repr__(self):
760 """x.__repr__() <==> repr(x)"""
761 return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key])))
762 for key in (self.scalars + self.sections)])
763
764 __str__ = __repr__
765 __str__.__doc__ = "x.__str__() <==> str(x)"
766
767
768 # Extra methods - not in a normal dictionary
769
770 def dict(self):
771 """
772 Return a deepcopy of self as a dictionary.
773
774 All members that are ``Section`` instances are recursively turned to
775 ordinary dictionaries - by calling their ``dict`` method.
776
777 >>> n = a.dict()
778 >>> n == a
779 1
780 >>> n is a
781 0
782 """
783 newdict = {}
784 for entry in self:
785 this_entry = self[entry]
786 if isinstance(this_entry, Section):
787 this_entry = this_entry.dict()
788 elif isinstance(this_entry, list):
789 # create a copy rather than a reference
790 this_entry = list(this_entry)
791 elif isinstance(this_entry, tuple):
792 # create a copy rather than a reference
793 this_entry = tuple(this_entry)
794 newdict[entry] = this_entry
795 return newdict
796
797
798 def merge(self, indict):
799 """
800 A recursive update - useful for merging config files.
801
802 >>> a = '''[section1]
803 ... option1 = True
804 ... [[subsection]]
805 ... more_options = False
806 ... # end of file'''.splitlines()
807 >>> b = '''# File is user.ini
808 ... [section1]
809 ... option1 = False
810 ... # end of file'''.splitlines()
811 >>> c1 = ConfigObj(b)
812 >>> c2 = ConfigObj(a)
813 >>> c2.merge(c1)
814 >>> c2
815 {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}}
816 """
817 for key, val in indict.items():
818 if (key in self and isinstance(self[key], dict) and
819 isinstance(val, dict)):
820 self[key].merge(val)
821 else:
822 self[key] = val
823
824
825 def rename(self, oldkey, newkey):
826 """
827 Change a keyname to another, without changing position in sequence.
828
829 Implemented so that transformations can be made on keys,
830 as well as on values. (used by encode and decode)
831
832 Also renames comments.
833 """
834 if oldkey in self.scalars:
835 the_list = self.scalars
836 elif oldkey in self.sections:
837 the_list = self.sections
838 else:
839 raise KeyError('Key "%s" not found.' % oldkey)
840 pos = the_list.index(oldkey)
841 #
842 val = self[oldkey]
843 dict.__delitem__(self, oldkey)
844 dict.__setitem__(self, newkey, val)
845 the_list.remove(oldkey)
846 the_list.insert(pos, newkey)
847 comm = self.comments[oldkey]
848 inline_comment = self.inline_comments[oldkey]
849 del self.comments[oldkey]
850 del self.inline_comments[oldkey]
851 self.comments[newkey] = comm
852 self.inline_comments[newkey] = inline_comment
853
854
855 def walk(self, function, raise_errors=True,
856 call_on_sections=False, **keywargs):
857 """
858 Walk every member and call a function on the keyword and value.
859
860 Return a dictionary of the return values
861
862 If the function raises an exception, raise the errror
863 unless ``raise_errors=False``, in which case set the return value to
864 ``False``.
865
866 Any unrecognised keyword arguments you pass to walk, will be pased on
867 to the function you pass in.
868
869 Note: if ``call_on_sections`` is ``True`` then - on encountering a
870 subsection, *first* the function is called for the *whole* subsection,
871 and then recurses into it's members. This means your function must be
872 able to handle strings, dictionaries and lists. This allows you
873 to change the key of subsections as well as for ordinary members. The
874 return value when called on the whole subsection has to be discarded.
875
876 See the encode and decode methods for examples, including functions.
877
878 .. caution::
879
880 You can use ``walk`` to transform the names of members of a section
881 but you mustn't add or delete members.
882
883 >>> config = '''[XXXXsection]
884 ... XXXXkey = XXXXvalue'''.splitlines()
885 >>> cfg = ConfigObj(config)
886 >>> cfg
887 {'XXXXsection': {'XXXXkey': 'XXXXvalue'}}
888 >>> def transform(section, key):
889 ... val = section[key]
890 ... newkey = key.replace('XXXX', 'CLIENT1')
891 ... section.rename(key, newkey)
892 ... if isinstance(val, (tuple, list, dict)):
893 ... pass
894 ... else:
895 ... val = val.replace('XXXX', 'CLIENT1')
896 ... section[newkey] = val
897 >>> cfg.walk(transform, call_on_sections=True)
898 {'CLIENT1section': {'CLIENT1key': None}}
899 >>> cfg
900 {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}}
901 """
902 out = {}
903 # scalars first
904 for i in range(len(self.scalars)):
905 entry = self.scalars[i]
906 try:
907 val = function(self, entry, **keywargs)
908 # bound again in case name has changed
909 entry = self.scalars[i]
910 out[entry] = val
911 except Exception:
912 if raise_errors:
913 raise
914 else:
915 entry = self.scalars[i]
916 out[entry] = False
917 # then sections
918 for i in range(len(self.sections)):
919 entry = self.sections[i]
920 if call_on_sections:
921 try:
922 function(self, entry, **keywargs)
923 except Exception:
924 if raise_errors:
925 raise
926 else:
927 entry = self.sections[i]
928 out[entry] = False
929 # bound again in case name has changed
930 entry = self.sections[i]
931 # previous result is discarded
932 out[entry] = self[entry].walk(
933 function,
934 raise_errors=raise_errors,
935 call_on_sections=call_on_sections,
936 **keywargs)
937 return out
938
939
940 def decode(self, encoding):
941 """
942 Decode all strings and values to unicode, using the specified encoding.
943
944 Works with subsections and list values.
945
946 Uses the ``walk`` method.
947
948 Testing ``encode`` and ``decode``.
949 >>> m = ConfigObj(a)
950 >>> m.decode('ascii')
951 >>> def testuni(val):
952 ... for entry in val:
953 ... if not isinstance(entry, unicode):
954 ... print >> sys.stderr, type(entry)
955 ... raise AssertionError, 'decode failed.'
956 ... if isinstance(val[entry], dict):
957 ... testuni(val[entry])
958 ... elif not isinstance(val[entry], unicode):
959 ... raise AssertionError, 'decode failed.'
960 >>> testuni(m)
961 >>> m.encode('ascii')
962 >>> a == m
963 1
964 """
965 warn('use of ``decode`` is deprecated.', DeprecationWarning)
966 def decode(section, key, encoding=encoding, warn=True):
967 """ """
968 val = section[key]
969 if isinstance(val, (list, tuple)):
970 newval = []
971 for entry in val:
972 newval.append(entry.decode(encoding))
973 elif isinstance(val, dict):
974 newval = val
975 else:
976 newval = val.decode(encoding)
977 newkey = key.decode(encoding)
978 section.rename(key, newkey)
979 section[newkey] = newval
980 # using ``call_on_sections`` allows us to modify section names
981 self.walk(decode, call_on_sections=True)
982
983
984 def encode(self, encoding):
985 """
986 Encode all strings and values from unicode,
987 using the specified encoding.
988
989 Works with subsections and list values.
990 Uses the ``walk`` method.
991 """
992 warn('use of ``encode`` is deprecated.', DeprecationWarning)
993 def encode(section, key, encoding=encoding):
994 """ """
995 val = section[key]
996 if isinstance(val, (list, tuple)):
997 newval = []
998 for entry in val:
999 newval.append(entry.encode(encoding))
1000 elif isinstance(val, dict):
1001 newval = val
1002 else:
1003 newval = val.encode(encoding)
1004 newkey = key.encode(encoding)
1005 section.rename(key, newkey)
1006 section[newkey] = newval
1007 self.walk(encode, call_on_sections=True)
1008
1009
1010 def istrue(self, key):
1011 """A deprecated version of ``as_bool``."""
1012 warn('use of ``istrue`` is deprecated. Use ``as_bool`` method '
1013 'instead.', DeprecationWarning)
1014 return self.as_bool(key)
1015
1016
1017 def as_bool(self, key):
1018 """
1019 Accepts a key as input. The corresponding value must be a string or
1020 the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to
1021 retain compatibility with Python 2.2.
1022
1023 If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns
1024 ``True``.
1025
1026 If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns
1027 ``False``.
1028
1029 ``as_bool`` is not case sensitive.
1030
1031 Any other input will raise a ``ValueError``.
1032
1033 >>> a = ConfigObj()
1034 >>> a['a'] = 'fish'
1035 >>> a.as_bool('a')
1036 Traceback (most recent call last):
1037 ValueError: Value "fish" is neither True nor False
1038 >>> a['b'] = 'True'
1039 >>> a.as_bool('b')
1040 1
1041 >>> a['b'] = 'off'
1042 >>> a.as_bool('b')
1043 0
1044 """
1045 val = self[key]
1046 if val == True:
1047 return True
1048 elif val == False:
1049 return False
1050 else:
1051 try:
1052 if not isinstance(val, StringTypes):
1053 # TODO: Why do we raise a KeyError here?
1054 raise KeyError()
1055 else:
1056 return self.main._bools[val.lower()]
1057 except KeyError:
1058 raise ValueError('Value "%s" is neither True nor False' % val)
1059
1060
1061 def as_int(self, key):
1062 """
1063 A convenience method which coerces the specified value to an integer.
1064
1065 If the value is an invalid literal for ``int``, a ``ValueError`` will
1066 be raised.
1067
1068 >>> a = ConfigObj()
1069 >>> a['a'] = 'fish'
1070 >>> a.as_int('a')
1071 Traceback (most recent call last):
1072 ValueError: invalid literal for int(): fish
1073 >>> a['b'] = '1'
1074 >>> a.as_int('b')
1075 1
1076 >>> a['b'] = '3.2'
1077 >>> a.as_int('b')
1078 Traceback (most recent call last):
1079 ValueError: invalid literal for int(): 3.2
1080 """
1081 return int(self[key])
1082
1083
1084 def as_float(self, key):
1085 """
1086 A convenience method which coerces the specified value to a float.
1087
1088 If the value is an invalid literal for ``float``, a ``ValueError`` will
1089 be raised.
1090
1091 >>> a = ConfigObj()
1092 >>> a['a'] = 'fish'
1093 >>> a.as_float('a')
1094 Traceback (most recent call last):
1095 ValueError: invalid literal for float(): fish
1096 >>> a['b'] = '1'
1097 >>> a.as_float('b')
1098 1.0
1099 >>> a['b'] = '3.2'
1100 >>> a.as_float('b')
1101 3.2000000000000002
1102 """
1103 return float(self[key])
1104
1105
1106 def restore_default(self, key):
1107 """
1108 Restore (and return) default value for the specified key.
1109
1110 This method will only work for a ConfigObj that was created
1111 with a configspec and has been validated.
1112
1113 If there is no default value for this key, ``KeyError`` is raised.
1114 """
1115 default = self.default_values[key]
1116 dict.__setitem__(self, key, default)
1117 if key not in self.defaults:
1118 self.defaults.append(key)
1119 return default
1120
1121
1122 def restore_defaults(self):
1123 """
1124 Recursively restore default values to all members
1125 that have them.
1126
1127 This method will only work for a ConfigObj that was created
1128 with a configspec and has been validated.
1129
1130 It doesn't delete or modify entries without default values.
1131 """
1132 for key in self.default_values:
1133 self.restore_default(key)
1134
1135 for section in self.sections:
1136 self[section].restore_defaults()
1137
1138
1139 class ConfigObj(Section):
1140 """An object to read, create, and write config files."""
1141
1142 _keyword = re.compile(r'''^ # line start
1143 (\s*) # indentation
1144 ( # keyword
1145 (?:".*?")| # double quotes
1146 (?:'.*?')| # single quotes
1147 (?:[^'"=].*?) # no quotes
1148 )
1149 \s*=\s* # divider
1150 (.*) # value (including list values and comments)
1151 $ # line end
1152 ''',
1153 re.VERBOSE)
1154
1155 _sectionmarker = re.compile(r'''^
1156 (\s*) # 1: indentation
1157 ((?:\[\s*)+) # 2: section marker open
1158 ( # 3: section name open
1159 (?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1160 (?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1161 (?:[^'"\s].*?) # at least one non-space unquoted
1162 ) # section name close
1163 ((?:\s*\])+) # 4: section marker close
1164 \s*(\#.*)? # 5: optional comment
1165 $''',
1166 re.VERBOSE)
1167
1168 # this regexp pulls list values out as a single string
1169 # or single values and comments
1170 # FIXME: this regex adds a '' to the end of comma terminated lists
1171 # workaround in ``_handle_value``
1172 _valueexp = re.compile(r'''^
1173 (?:
1174 (?:
1175 (
1176 (?:
1177 (?:
1178 (?:".*?")| # double quotes
1179 (?:'.*?')| # single quotes
1180 (?:[^'",\#][^,\#]*?) # unquoted
1181 )
1182 \s*,\s* # comma
1183 )* # match all list items ending in a comma (if any)
1184 )
1185 (
1186 (?:".*?")| # double quotes
1187 (?:'.*?')| # single quotes
1188 (?:[^'",\#\s][^,]*?)| # unquoted
1189 (?:(?<!,)) # Empty value
1190 )? # last item in a list - or string value
1191 )|
1192 (,) # alternatively a single comma - empty list
1193 )
1194 \s*(\#.*)? # optional comment
1195 $''',
1196 re.VERBOSE)
1197
1198 # use findall to get the members of a list value
1199 _listvalueexp = re.compile(r'''
1200 (
1201 (?:".*?")| # double quotes
1202 (?:'.*?')| # single quotes
1203 (?:[^'",\#].*?) # unquoted
1204 )
1205 \s*,\s* # comma
1206 ''',
1207 re.VERBOSE)
1208
1209 # this regexp is used for the value
1210 # when lists are switched off
1211 _nolistvalue = re.compile(r'''^
1212 (
1213 (?:".*?")| # double quotes
1214 (?:'.*?')| # single quotes
1215 (?:[^'"\#].*?)| # unquoted
1216 (?:) # Empty value
1217 )
1218 \s*(\#.*)? # optional comment
1219 $''',
1220 re.VERBOSE)
1221
1222 # regexes for finding triple quoted values on one line
1223 _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1224 _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1225 _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1226 _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1227
1228 _triple_quote = {
1229 "'''": (_single_line_single, _multi_line_single),
1230 '"""': (_single_line_double, _multi_line_double),
1231 }
1232
1233 # Used by the ``istrue`` Section method
1234 _bools = {
1235 'yes': True, 'no': False,
1236 'on': True, 'off': False,
1237 '1': True, '0': False,
1238 'true': True, 'false': False,
1239 }
1240
1241
1242 def __init__(self, infile=None, options=None, **kwargs):
1243 """
1244 Parse a config file or create a config file object.
1245
1246 ``ConfigObj(infile=None, options=None, **kwargs)``
1247 """
1248 # init the superclass
1249 Section.__init__(self, self, 0, self)
1250
1251 if infile is None:
1252 infile = []
1253 if options is None:
1254 options = {}
1255 else:
1256 options = dict(options)
1257
1258 # keyword arguments take precedence over an options dictionary
1259 options.update(kwargs)
1260
1261 defaults = OPTION_DEFAULTS.copy()
1262 # TODO: check the values too.
1263 for entry in options:
1264 if entry not in defaults:
1265 raise TypeError('Unrecognised option "%s".' % entry)
1266
1267 # Add any explicit options to the defaults
1268 defaults.update(options)
1269 self._initialise(defaults)
1270 configspec = defaults['configspec']
1271 self._original_configspec = configspec
1272 self._load(infile, configspec)
1273
1274
1275 def _load(self, infile, configspec):
1276 if isinstance(infile, StringTypes):
1277 self.filename = infile
1278 if os.path.isfile(infile):
1279 h = open(infile, 'rb')
1280 infile = h.read() or []
1281 h.close()
1282 elif self.file_error:
1283 # raise an error if the file doesn't exist
1284 raise IOError('Config file not found: "%s".' % self.filename)
1285 else:
1286 # file doesn't already exist
1287 if self.create_empty:
1288 # this is a good test that the filename specified
1289 # isn't impossible - like on a non-existent device
1290 h = open(infile, 'w')
1291 h.write('')
1292 h.close()
1293 infile = []
1294
1295 elif isinstance(infile, (list, tuple)):
1296 infile = list(infile)
1297
1298 elif isinstance(infile, dict):
1299 # initialise self
1300 # the Section class handles creating subsections
1301 if isinstance(infile, ConfigObj):
1302 # get a copy of our ConfigObj
1303 infile = infile.dict()
1304
1305 for entry in infile:
1306 self[entry] = infile[entry]
1307 del self._errors
1308
1309 if configspec is not None:
1310 self._handle_configspec(configspec)
1311 else:
1312 self.configspec = None
1313 return
1314
1315 elif hasattr(infile, 'read'):
1316 # This supports file like objects
1317 infile = infile.read() or []
1318 # needs splitting into lines - but needs doing *after* decoding
1319 # in case it's not an 8 bit encoding
1320 else:
1321 raise TypeError('infile must be a filename, file like object, or list of lines.')
1322
1323 if infile:
1324 # don't do it for the empty ConfigObj
1325 infile = self._handle_bom(infile)
1326 # infile is now *always* a list
1327 #
1328 # Set the newlines attribute (first line ending it finds)
1329 # and strip trailing '\n' or '\r' from lines
1330 for line in infile:
1331 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')):
1332 continue
1333 for end in ('\r\n', '\n', '\r'):
1334 if line.endswith(end):
1335 self.newlines = end
1336 break
1337 break
1338
1339 infile = [line.rstrip('\r\n') for line in infile]
1340
1341 self._parse(infile)
1342 # if we had any errors, now is the time to raise them
1343 if self._errors:
1344 info = "at line %s." % self._errors[0].line_number
1345 if len(self._errors) > 1:
1346 msg = "Parsing failed with several errors.\nFirst error %s" % info
1347 error = ConfigObjError(msg)
1348 else:
1349 error = self._errors[0]
1350 # set the errors attribute; it's a list of tuples:
1351 # (error_type, message, line_number)
1352 error.errors = self._errors
1353 # set the config attribute
1354 error.config = self
1355 raise error
1356 # delete private attributes
1357 del self._errors
1358
1359 if configspec is None:
1360 self.configspec = None
1361 else:
1362 self._handle_configspec(configspec)
1363
1364
1365 def _initialise(self, options=None):
1366 if options is None:
1367 options = OPTION_DEFAULTS
1368
1369 # initialise a few variables
1370 self.filename = None
1371 self._errors = []
1372 self.raise_errors = options['raise_errors']
1373 self.interpolation = options['interpolation']
1374 self.list_values = options['list_values']
1375 self.create_empty = options['create_empty']
1376 self.file_error = options['file_error']
1377 self.stringify = options['stringify']
1378 self.indent_type = options['indent_type']
1379 self.encoding = options['encoding']
1380 self.default_encoding = options['default_encoding']
1381 self.BOM = False
1382 self.newlines = None
1383 self.write_empty_values = options['write_empty_values']
1384 self.unrepr = options['unrepr']
1385
1386 self.initial_comment = []
1387 self.final_comment = []
1388 self.configspec = {}
1389
1390 # Clear section attributes as well
1391 Section._initialise(self)
1392
1393
1394 def __repr__(self):
1395 return ('ConfigObj({%s})' %
1396 ', '.join([('%s: %s' % (repr(key), repr(self[key])))
1397 for key in (self.scalars + self.sections)]))
1398
1399
1400 def _handle_bom(self, infile):
1401 """
1402 Handle any BOM, and decode if necessary.
1403
1404 If an encoding is specified, that *must* be used - but the BOM should
1405 still be removed (and the BOM attribute set).
1406
1407 (If the encoding is wrongly specified, then a BOM for an alternative
1408 encoding won't be discovered or removed.)
1409
1410 If an encoding is not specified, UTF8 or UTF16 BOM will be detected and
1411 removed. The BOM attribute will be set. UTF16 will be decoded to
1412 unicode.
1413
1414 NOTE: This method must not be called with an empty ``infile``.
1415
1416 Specifying the *wrong* encoding is likely to cause a
1417 ``UnicodeDecodeError``.
1418
1419 ``infile`` must always be returned as a list of lines, but may be
1420 passed in as a single string.
1421 """
1422 if ((self.encoding is not None) and
1423 (self.encoding.lower() not in BOM_LIST)):
1424 # No need to check for a BOM
1425 # the encoding specified doesn't have one
1426 # just decode
1427 return self._decode(infile, self.encoding)
1428
1429 if isinstance(infile, (list, tuple)):
1430 line = infile[0]
1431 else:
1432 line = infile
1433 if self.encoding is not None:
1434 # encoding explicitly supplied
1435 # And it could have an associated BOM
1436 # TODO: if encoding is just UTF16 - we ought to check for both
1437 # TODO: big endian and little endian versions.
1438 enc = BOM_LIST[self.encoding.lower()]
1439 if enc == 'utf_16':
1440 # For UTF16 we try big endian and little endian
1441 for BOM, (encoding, final_encoding) in BOMS.items():
1442 if not final_encoding:
1443 # skip UTF8
1444 continue
1445 if infile.startswith(BOM):
1446 ### BOM discovered
1447 ##self.BOM = True
1448 # Don't need to remove BOM
1449 return self._decode(infile, encoding)
1450
1451 # If we get this far, will *probably* raise a DecodeError
1452 # As it doesn't appear to start with a BOM
1453 return self._decode(infile, self.encoding)
1454
1455 # Must be UTF8
1456 BOM = BOM_SET[enc]
1457 if not line.startswith(BOM):
1458 return self._decode(infile, self.encoding)
1459
1460 newline = line[len(BOM):]
1461
1462 # BOM removed
1463 if isinstance(infile, (list, tuple)):
1464 infile[0] = newline
1465 else:
1466 infile = newline
1467 self.BOM = True
1468 return self._decode(infile, self.encoding)
1469
1470 # No encoding specified - so we need to check for UTF8/UTF16
1471 for BOM, (encoding, final_encoding) in BOMS.items():
1472 if not line.startswith(BOM):
1473 continue
1474 else:
1475 # BOM discovered
1476 self.encoding = final_encoding
1477 if not final_encoding:
1478 self.BOM = True
1479 # UTF8
1480 # remove BOM
1481 newline = line[len(BOM):]
1482 if isinstance(infile, (list, tuple)):
1483 infile[0] = newline
1484 else:
1485 infile = newline
1486 # UTF8 - don't decode
1487 if isinstance(infile, StringTypes):
1488 return infile.splitlines(True)
1489 else:
1490 return infile
1491 # UTF16 - have to decode
1492 return self._decode(infile, encoding)
1493
1494 # No BOM discovered and no encoding specified, just return
1495 if isinstance(infile, StringTypes):
1496 # infile read from a file will be a single string
1497 return infile.splitlines(True)
1498 return infile
1499
1500
1501 def _a_to_u(self, aString):
1502 """Decode ASCII strings to unicode if a self.encoding is specified."""
1503 if self.encoding:
1504 return aString.decode('ascii')
1505 else:
1506 return aString
1507
1508
1509 def _decode(self, infile, encoding):
1510 """
1511 Decode infile to unicode. Using the specified encoding.
1512
1513 if is a string, it also needs converting to a list.
1514 """
1515 if isinstance(infile, StringTypes):
1516 # can't be unicode
1517 # NOTE: Could raise a ``UnicodeDecodeError``
1518 return infile.decode(encoding).splitlines(True)
1519 for i, line in enumerate(infile):
1520 if not isinstance(line, unicode):
1521 # NOTE: The isinstance test here handles mixed lists of unicode/string
1522 # NOTE: But the decode will break on any non-string values
1523 # NOTE: Or could raise a ``UnicodeDecodeError``
1524 infile[i] = line.decode(encoding)
1525 return infile
1526
1527
1528 def _decode_element(self, line):
1529 """Decode element to unicode if necessary."""
1530 if not self.encoding:
1531 return line
1532 if isinstance(line, str) and self.default_encoding:
1533 return line.decode(self.default_encoding)
1534 return line
1535
1536
1537 def _str(self, value):
1538 """
1539 Used by ``stringify`` within validate, to turn non-string values
1540 into strings.
1541 """
1542 if not isinstance(value, StringTypes):
1543 return str(value)
1544 else:
1545 return value
1546
1547
1548 def _parse(self, infile):
1549 """Actually parse the config file."""
1550 temp_list_values = self.list_values
1551 if self.unrepr:
1552 self.list_values = False
1553
1554 comment_list = []
1555 done_start = False
1556 this_section = self
1557 maxline = len(infile) - 1
1558 cur_index = -1
1559 reset_comment = False
1560
1561 while cur_index < maxline:
1562 if reset_comment:
1563 comment_list = []
1564 cur_index += 1
1565 line = infile[cur_index]
1566 sline = line.strip()
1567 # do we have anything on the line ?
1568 if not sline or sline.startswith('#'):
1569 reset_comment = False
1570 comment_list.append(line)
1571 continue
1572
1573 if not done_start:
1574 # preserve initial comment
1575 self.initial_comment = comment_list
1576 comment_list = []
1577 done_start = True
1578
1579 reset_comment = True
1580 # first we check if it's a section marker
1581 mat = self._sectionmarker.match(line)
1582 if mat is not None:
1583 # is a section line
1584 (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1585 if indent and (self.indent_type is None):
1586 self.indent_type = indent
1587 cur_depth = sect_open.count('[')
1588 if cur_depth != sect_close.count(']'):
1589 self._handle_error("Cannot compute the section depth at line %s.",
1590 NestingError, infile, cur_index)
1591 continue
1592
1593 if cur_depth < this_section.depth:
1594 # the new section is dropping back to a previous level
1595 try:
1596 parent = self._match_depth(this_section,
1597 cur_depth).parent
1598 except SyntaxError:
1599 self._handle_error("Cannot compute nesting level at line %s.",
1600 NestingError, infile, cur_index)
1601 continue
1602 elif cur_depth == this_section.depth:
1603 # the new section is a sibling of the current section
1604 parent = this_section.parent
1605 elif cur_depth == this_section.depth + 1:
1606 # the new section is a child the current section
1607 parent = this_section
1608 else:
1609 self._handle_error("Section too nested at line %s.",
1610 NestingError, infile, cur_index)
1611
1612 sect_name = self._unquote(sect_name)
1613 if parent.has_key(sect_name):
1614 self._handle_error('Duplicate section name at line %s.',
1615 DuplicateError, infile, cur_index)
1616 continue
1617
1618 # create the new section
1619 this_section = Section(
1620 parent,
1621 cur_depth,
1622 self,
1623 name=sect_name)
1624 parent[sect_name] = this_section
1625 parent.inline_comments[sect_name] = comment
1626 parent.comments[sect_name] = comment_list
1627 continue
1628 #
1629 # it's not a section marker,
1630 # so it should be a valid ``key = value`` line
1631 mat = self._keyword.match(line)
1632 if mat is None:
1633 # it neither matched as a keyword
1634 # or a section marker
1635 self._handle_error(
1636 'Invalid line at line "%s".',
1637 ParseError, infile, cur_index)
1638 else:
1639 # is a keyword value
1640 # value will include any inline comment
1641 (indent, key, value) = mat.groups()
1642 if indent and (self.indent_type is None):
1643 self.indent_type = indent
1644 # check for a multiline value
1645 if value[:3] in ['"""', "'''"]:
1646 try:
1647 (value, comment, cur_index) = self._multiline(
1648 value, infile, cur_index, maxline)
1649 except SyntaxError:
1650 self._handle_error(
1651 'Parse error in value at line %s.',
1652 ParseError, infile, cur_index)
1653 continue
1654 else:
1655 if self.unrepr:
1656 comment = ''
1657 try:
1658 value = unrepr(value)
1659 except Exception, e:
1660 if type(e) == UnknownType:
1661 msg = 'Unknown name or type in value at line %s.'
1662 else:
1663 msg = 'Parse error in value at line %s.'
1664 self._handle_error(msg, UnreprError, infile,
1665 cur_index)
1666 continue
1667 else:
1668 if self.unrepr:
1669 comment = ''
1670 try:
1671 value = unrepr(value)
1672 except Exception, e:
1673 if isinstance(e, UnknownType):
1674 msg = 'Unknown name or type in value at line %s.'
1675 else:
1676 msg = 'Parse error in value at line %s.'
1677 self._handle_error(msg, UnreprError, infile,
1678 cur_index)
1679 continue
1680 else:
1681 # extract comment and lists
1682 try:
1683 (value, comment) = self._handle_value(value)
1684 except SyntaxError:
1685 self._handle_error(
1686 'Parse error in value at line %s.',
1687 ParseError, infile, cur_index)
1688 continue
1689 #
1690 key = self._unquote(key)
1691 if this_section.has_key(key):
1692 self._handle_error(
1693 'Duplicate keyword name at line %s.',
1694 DuplicateError, infile, cur_index)
1695 continue
1696 # add the key.
1697 # we set unrepr because if we have got this far we will never
1698 # be creating a new section
1699 this_section.__setitem__(key, value, unrepr=True)
1700 this_section.inline_comments[key] = comment
1701 this_section.comments[key] = comment_list
1702 continue
1703 #
1704 if self.indent_type is None:
1705 # no indentation used, set the type accordingly
1706 self.indent_type = ''
1707
1708 # preserve the final comment
1709 if not self and not self.initial_comment:
1710 self.initial_comment = comment_list
1711 elif not reset_comment:
1712 self.final_comment = comment_list
1713 self.list_values = temp_list_values
1714
1715
1716 def _match_depth(self, sect, depth):
1717 """
1718 Given a section and a depth level, walk back through the sections
1719 parents to see if the depth level matches a previous section.
1720
1721 Return a reference to the right section,
1722 or raise a SyntaxError.
1723 """
1724 while depth < sect.depth:
1725 if sect is sect.parent:
1726 # we've reached the top level already
1727 raise SyntaxError()
1728 sect = sect.parent
1729 if sect.depth == depth:
1730 return sect
1731 # shouldn't get here
1732 raise SyntaxError()
1733
1734
1735 def _handle_error(self, text, ErrorClass, infile, cur_index):
1736 """
1737 Handle an error according to the error settings.
1738
1739 Either raise the error or store it.
1740 The error will have occured at ``cur_index``
1741 """
1742 line = infile[cur_index]
1743 cur_index += 1
1744 message = text % cur_index
1745 error = ErrorClass(message, cur_index, line)
1746 if self.raise_errors:
1747 # raise the error - parsing stops here
1748 raise error
1749 # store the error
1750 # reraise when parsing has finished
1751 self._errors.append(error)
1752
1753
1754 def _unquote(self, value):
1755 """Return an unquoted version of a value"""
1756 if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1757 value = value[1:-1]
1758 return value
1759
1760
1761 def _quote(self, value, multiline=True):
1762 """
1763 Return a safely quoted version of a value.
1764
1765 Raise a ConfigObjError if the value cannot be safely quoted.
1766 If multiline is ``True`` (default) then use triple quotes
1767 if necessary.
1768
1769 Don't quote values that don't need it.
1770 Recursively quote members of a list and return a comma joined list.
1771 Multiline is ``False`` for lists.
1772 Obey list syntax for empty and single member lists.
1773
1774 If ``list_values=False`` then the value is only quoted if it contains
1775 a ``\n`` (is multiline) or '#'.
1776
1777 If ``write_empty_values`` is set, and the value is an empty string, it
1778 won't be quoted.
1779 """
1780 if multiline and self.write_empty_values and value == '':
1781 # Only if multiline is set, so that it is used for values not
1782 # keys, and not values that are part of a list
1783 return ''
1784
1785 if multiline and isinstance(value, (list, tuple)):
1786 if not value:
1787 return ','
1788 elif len(value) == 1:
1789 return self._quote(value[0], multiline=False) + ','
1790 return ', '.join([self._quote(val, multiline=False)
1791 for val in value])
1792 if not isinstance(value, StringTypes):
1793 if self.stringify:
1794 value = str(value)
1795 else:
1796 raise TypeError('Value "%s" is not a string.' % value)
1797
1798 if not value:
1799 return '""'
1800
1801 no_lists_no_quotes = not self.list_values and '\n' not in value and '#' not in value
1802 need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1803 hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1804 check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1805
1806 if check_for_single:
1807 if not self.list_values:
1808 # we don't quote if ``list_values=False``
1809 quot = noquot
1810 # for normal values either single or double quotes will do
1811 elif '\n' in value:
1812 # will only happen if multiline is off - e.g. '\n' in key
1813 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1814 elif ((value[0] not in wspace_plus) and
1815 (value[-1] not in wspace_plus) and
1816 (',' not in value)):
1817 quot = noquot
1818 else:
1819 quot = self._get_single_quote(value)
1820 else:
1821 # if value has '\n' or "'" *and* '"', it will need triple quotes
1822 quot = self._get_triple_quote(value)
1823
1824 if quot == noquot and '#' in value and self.list_values:
1825 quot = self._get_single_quote(value)
1826
1827 return quot % value
1828
1829
1830 def _get_single_quote(self, value):
1831 if ("'" in value) and ('"' in value):
1832 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1833 elif '"' in value:
1834 quot = squot
1835 else:
1836 quot = dquot
1837 return quot
1838
1839
1840 def _get_triple_quote(self, value):
1841 if (value.find('"""') != -1) and (value.find("'''") != -1):
1842 raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1843 if value.find('"""') == -1:
1844 quot = tdquot
1845 else:
1846 quot = tsquot
1847 return quot
1848
1849
1850 def _handle_value(self, value):
1851 """
1852 Given a value string, unquote, remove comment,
1853 handle lists. (including empty and single member lists)
1854 """
1855 # do we look for lists in values ?
1856 if not self.list_values:
1857 mat = self._nolistvalue.match(value)
1858 if mat is None:
1859 raise SyntaxError()
1860 # NOTE: we don't unquote here
1861 return mat.groups()
1862 #
1863 mat = self._valueexp.match(value)
1864 if mat is None:
1865 # the value is badly constructed, probably badly quoted,
1866 # or an invalid list
1867 raise SyntaxError()
1868 (list_values, single, empty_list, comment) = mat.groups()
1869 if (list_values == '') and (single is None):
1870 # change this if you want to accept empty values
1871 raise SyntaxError()
1872 # NOTE: note there is no error handling from here if the regex
1873 # is wrong: then incorrect values will slip through
1874 if empty_list is not None:
1875 # the single comma - meaning an empty list
1876 return ([], comment)
1877 if single is not None:
1878 # handle empty values
1879 if list_values and not single:
1880 # FIXME: the '' is a workaround because our regex now matches
1881 # '' at the end of a list if it has a trailing comma
1882 single = None
1883 else:
1884 single = single or '""'
1885 single = self._unquote(single)
1886 if list_values == '':
1887 # not a list value
1888 return (single, comment)
1889 the_list = self._listvalueexp.findall(list_values)
1890 the_list = [self._unquote(val) for val in the_list]
1891 if single is not None:
1892 the_list += [single]
1893 return (the_list, comment)
1894
1895
1896 def _multiline(self, value, infile, cur_index, maxline):
1897 """Extract the value, where we are in a multiline situation."""
1898 quot = value[:3]
1899 newvalue = value[3:]
1900 single_line = self._triple_quote[quot][0]
1901 multi_line = self._triple_quote[quot][1]
1902 mat = single_line.match(value)
1903 if mat is not None:
1904 retval = list(mat.groups())
1905 retval.append(cur_index)
1906 return retval
1907 elif newvalue.find(quot) != -1:
1908 # somehow the triple quote is missing
1909 raise SyntaxError()
1910 #
1911 while cur_index < maxline:
1912 cur_index += 1
1913 newvalue += '\n'
1914 line = infile[cur_index]
1915 if line.find(quot) == -1:
1916 newvalue += line
1917 else:
1918 # end of multiline, process it
1919 break
1920 else:
1921 # we've got to the end of the config, oops...
1922 raise SyntaxError()
1923 mat = multi_line.match(line)
1924 if mat is None:
1925 # a badly formed line
1926 raise SyntaxError()
1927 (value, comment) = mat.groups()
1928 return (newvalue + value, comment, cur_index)
1929
1930
1931 def _handle_configspec(self, configspec):
1932 """Parse the configspec."""
1933 # FIXME: Should we check that the configspec was created with the
1934 # correct settings ? (i.e. ``list_values=False``)
1935 if not isinstance(configspec, ConfigObj):
1936 try:
1937 configspec = ConfigObj(configspec,
1938 raise_errors=True,
1939 file_error=True,
1940 list_values=False)
1941 except ConfigObjError, e:
1942 # FIXME: Should these errors have a reference
1943 # to the already parsed ConfigObj ?
1944 raise ConfigspecError('Parsing configspec failed: %s' % e)
1945 except IOError, e:
1946 raise IOError('Reading configspec failed: %s' % e)
1947
1948 self._set_configspec_value(configspec, self)
1949
1950
1951 def _set_configspec_value(self, configspec, section):
1952 """Used to recursively set configspec values."""
1953 if '__many__' in configspec.sections:
1954 section.configspec['__many__'] = configspec['__many__']
1955 if len(configspec.sections) > 1:
1956 # FIXME: can we supply any useful information here ?
1957 raise RepeatSectionError()
1958
1959 if hasattr(configspec, 'initial_comment'):
1960 section._configspec_initial_comment = configspec.initial_comment
1961 section._configspec_final_comment = configspec.final_comment
1962 section._configspec_encoding = configspec.encoding
1963 section._configspec_BOM = configspec.BOM
1964 section._configspec_newlines = configspec.newlines
1965 section._configspec_indent_type = configspec.indent_type
1966
1967 for entry in configspec.scalars:
1968 section._configspec_comments[entry] = configspec.comments[entry]
1969 section._configspec_inline_comments[entry] = configspec.inline_comments[entry]
1970 section.configspec[entry] = configspec[entry]
1971 section._order.append(entry)
1972
1973 for entry in configspec.sections:
1974 if entry == '__many__':
1975 continue
1976
1977 section._cs_section_comments[entry] = configspec.comments[entry]
1978 section._cs_section_inline_comments[entry] = configspec.inline_comments[entry]
1979 if not section.has_key(entry):
1980 section[entry] = {}
1981 self._set_configspec_value(configspec[entry], section[entry])
1982
1983
1984 def _handle_repeat(self, section, configspec):
1985 """Dynamically assign configspec for repeated section."""
1986 try:
1987 section_keys = configspec.sections
1988 scalar_keys = configspec.scalars
1989 except AttributeError:
1990 section_keys = [entry for entry in configspec
1991 if isinstance(configspec[entry], dict)]
1992 scalar_keys = [entry for entry in configspec
1993 if not isinstance(configspec[entry], dict)]
1994
1995 if '__many__' in section_keys and len(section_keys) > 1:
1996 # FIXME: can we supply any useful information here ?
1997 raise RepeatSectionError()
1998
1999 scalars = {}
2000 sections = {}
2001 for entry in scalar_keys:
2002 val = configspec[entry]
2003 scalars[entry] = val
2004 for entry in section_keys:
2005 val = configspec[entry]
2006 if entry == '__many__':
2007 scalars[entry] = val
2008 continue
2009 sections[entry] = val
2010
2011 section.configspec = scalars
2012 for entry in sections:
2013 if not section.has_key(entry):
2014 section[entry] = {}
2015 self._handle_repeat(section[entry], sections[entry])
2016
2017
2018 def _write_line(self, indent_string, entry, this_entry, comment):
2019 """Write an individual line, for the write method"""
2020 # NOTE: the calls to self._quote here handles non-StringType values.
2021 if not self.unrepr:
2022 val = self._decode_element(self._quote(this_entry))
2023 else:
2024 val = repr(this_entry)
2025 return '%s%s%s%s%s' % (indent_string,
2026 self._decode_element(self._quote(entry, multiline=False)),
2027 self._a_to_u(' = '),
2028 val,
2029 self._decode_element(comment))
2030
2031
2032 def _write_marker(self, indent_string, depth, entry, comment):
2033 """Write a section marker line"""
2034 return '%s%s%s%s%s' % (indent_string,
2035 self._a_to_u('[' * depth),
2036 self._quote(self._decode_element(entry), multiline=False),
2037 self._a_to_u(']' * depth),
2038 self._decode_element(comment))
2039
2040
2041 def _handle_comment(self, comment):
2042 """Deal with a comment."""
2043 if not comment:
2044 return ''
2045 start = self.indent_type
2046 if not comment.startswith('#'):
2047 start += self._a_to_u(' # ')
2048 return (start + comment)
2049
2050
2051 # Public methods
2052
2053 def write(self, outfile=None, section=None):
2054 """
2055 Write the current ConfigObj as a file
2056
2057 tekNico: FIXME: use StringIO instead of real files
2058
2059 >>> filename = a.filename
2060 >>> a.filename = 'test.ini'
2061 >>> a.write()
2062 >>> a.filename = filename
2063 >>> a == ConfigObj('test.ini', raise_errors=True)
2064 1
2065 """
2066 if self.indent_type is None:
2067 # this can be true if initialised from a dictionary
2068 self.indent_type = DEFAULT_INDENT_TYPE
2069
2070 out = []
2071 cs = self._a_to_u('#')
2072 csp = self._a_to_u('# ')
2073 if section is None:
2074 int_val = self.interpolation
2075 self.interpolation = False
2076 section = self
2077 for line in self.initial_comment:
2078 line = self._decode_element(line)
2079 stripped_line = line.strip()
2080 if stripped_line and not stripped_line.startswith(cs):
2081 line = csp + line
2082 out.append(line)
2083
2084 indent_string = self.indent_type * section.depth
2085 for entry in (section.scalars + section.sections):
2086 if entry in section.defaults:
2087 # don't write out default values
2088 continue
2089 for comment_line in section.comments[entry]:
2090 comment_line = self._decode_element(comment_line.lstrip())
2091 if comment_line and not comment_line.startswith(cs):
2092 comment_line = csp + comment_line
2093 out.append(indent_string + comment_line)
2094 this_entry = section[entry]
2095 comment = self._handle_comment(section.inline_comments[entry])
2096
2097 if isinstance(this_entry, dict):
2098 # a section
2099 out.append(self._write_marker(
2100 indent_string,
2101 this_entry.depth,
2102 entry,
2103 comment))
2104 out.extend(self.write(section=this_entry))
2105 else:
2106 out.append(self._write_line(
2107 indent_string,
2108 entry,
2109 this_entry,
2110 comment))
2111
2112 if section is self:
2113 for line in self.final_comment:
2114 line = self._decode_element(line)
2115 stripped_line = line.strip()
2116 if stripped_line and not stripped_line.startswith(cs):
2117 line = csp + line
2118 out.append(line)
2119 self.interpolation = int_val
2120
2121 if section is not self:
2122 return out
2123
2124 if (self.filename is None) and (outfile is None):
2125 # output a list of lines
2126 # might need to encode
2127 # NOTE: This will *screw* UTF16, each line will start with the BOM
2128 if self.encoding:
2129 out = [l.encode(self.encoding) for l in out]
2130 if (self.BOM and ((self.encoding is None) or
2131 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))):
2132 # Add the UTF8 BOM
2133 if not out:
2134 out.append('')
2135 out[0] = BOM_UTF8 + out[0]
2136 return out
2137
2138 # Turn the list to a string, joined with correct newlines
2139 newline = self.newlines or os.linesep
2140 output = self._a_to_u(newline).join(out)
2141 if self.encoding:
2142 output = output.encode(self.encoding)
2143 if self.BOM and ((self.encoding is None) or match_utf8(self.encoding)):
2144 # Add the UTF8 BOM
2145 output = BOM_UTF8 + output
2146
2147 if not output.endswith(newline):
2148 output += newline
2149 if outfile is not None:
2150 outfile.write(output)
2151 else:
2152 h = open(self.filename, 'wb')
2153 h.write(output)
2154 h.close()
2155
2156
2157 def validate(self, validator, preserve_errors=False, copy=False,
2158 section=None):
2159 """
2160 Test the ConfigObj against a configspec.
2161
2162 It uses the ``validator`` object from *validate.py*.
2163
2164 To run ``validate`` on the current ConfigObj, call: ::
2165
2166 test = config.validate(validator)
2167
2168 (Normally having previously passed in the configspec when the ConfigObj
2169 was created - you can dynamically assign a dictionary of checks to the
2170 ``configspec`` attribute of a section though).
2171
2172 It returns ``True`` if everything passes, or a dictionary of
2173 pass/fails (True/False). If every member of a subsection passes, it
2174 will just have the value ``True``. (It also returns ``False`` if all
2175 members fail).
2176
2177 In addition, it converts the values from strings to their native
2178 types if their checks pass (and ``stringify`` is set).
2179
2180 If ``preserve_errors`` is ``True`` (``False`` is default) then instead
2181 of a marking a fail with a ``False``, it will preserve the actual
2182 exception object. This can contain info about the reason for failure.
2183 For example the ``VdtValueTooSmallError`` indicates that the value
2184 supplied was too small. If a value (or section) is missing it will
2185 still be marked as ``False``.
2186
2187 You must have the validate module to use ``preserve_errors=True``.
2188
2189 You can then use the ``flatten_errors`` function to turn your nested
2190 results dictionary into a flattened list of failures - useful for
2191 displaying meaningful error messages.
2192 """
2193 if section is None:
2194 if self.configspec is None:
2195 raise ValueError('No configspec supplied.')
2196 if preserve_errors:
2197 # We do this once to remove a top level dependency on the validate module
2198 # Which makes importing configobj faster
2199 from validate import VdtMissingValue
2200 self._vdtMissingValue = VdtMissingValue
2201 section = self
2202 #
2203 spec_section = section.configspec
2204 if copy and hasattr(section, '_configspec_initial_comment'):
2205 section.initial_comment = section._configspec_initial_comment
2206 section.final_comment = section._configspec_final_comment
2207 section.encoding = section._configspec_encoding
2208 section.BOM = section._configspec_BOM
2209 section.newlines = section._configspec_newlines
2210 section.indent_type = section._configspec_indent_type
2211
2212 if '__many__' in section.configspec:
2213 many = spec_section['__many__']
2214 # dynamically assign the configspecs
2215 # for the sections below
2216 for entry in section.sections:
2217 self._handle_repeat(section[entry], many)
2218 #
2219 out = {}
2220 ret_true = True
2221 ret_false = True
2222 order = [k for k in section._order if k in spec_section]
2223 order += [k for k in spec_section if k not in order]
2224 for entry in order:
2225 if entry == '__many__':
2226 continue
2227 if (not entry in section.scalars) or (entry in section.defaults):
2228 # missing entries
2229 # or entries from defaults
2230 missing = True
2231 val = None
2232 if copy and not entry in section.scalars:
2233 # copy comments
2234 section.comments[entry] = (
2235 section._configspec_comments.get(entry, []))
2236 section.inline_comments[entry] = (
2237 section._configspec_inline_comments.get(entry, ''))
2238 #
2239 else:
2240 missing = False
2241 val = section[entry]
2242 try:
2243 check = validator.check(spec_section[entry],
2244 val,
2245 missing=missing
2246 )
2247 except validator.baseErrorClass, e:
2248 if not preserve_errors or isinstance(e, self._vdtMissingValue):
2249 out[entry] = False
2250 else:
2251 # preserve the error
2252 out[entry] = e
2253 ret_false = False
2254 ret_true = False
2255 else:
2256 try:
2257 section.default_values.pop(entry, None)
2258 except AttributeError:
2259 # For Python 2.2 compatibility
2260 try:
2261 del section.default_values[entry]
2262 except KeyError:
2263 pass
2264
2265 if hasattr(validator, 'get_default_value'):
2266 try:
2267 section.default_values[entry] = validator.get_default_value(spec_section[entry])
2268 except KeyError:
2269 # No default
2270 pass
2271
2272 ret_false = False
2273 out[entry] = True
2274 if self.stringify or missing:
2275 # if we are doing type conversion
2276 # or the value is a supplied default
2277 if not self.stringify:
2278 if isinstance(check, (list, tuple)):
2279 # preserve lists
2280 check = [self._str(item) for item in check]
2281 elif missing and check is None:
2282 # convert the None from a default to a ''
2283 check = ''
2284 else:
2285 check = self._str(check)
2286 if (check != val) or missing:
2287 section[entry] = check
2288 if not copy and missing and entry not in section.defaults:
2289 section.defaults.append(entry)
2290 # Missing sections will have been created as empty ones when the
2291 # configspec was read.
2292 for entry in section.sections:
2293 # FIXME: this means DEFAULT is not copied in copy mode
2294 if section is self and entry == 'DEFAULT':
2295 continue
2296 if copy:
2297 section.comments[entry] = section._cs_section_comments[entry]
2298 section.inline_comments[entry] = (
2299 section._cs_section_inline_comments[entry])
2300 check = self.validate(validator, preserve_errors=preserve_errors,
2301 copy=copy, section=section[entry])
2302 out[entry] = check
2303 if check == False:
2304 ret_true = False
2305 elif check == True:
2306 ret_false = False
2307 else:
2308 ret_true = False
2309 ret_false = False
2310 #
2311 if ret_true:
2312 return True
2313 elif ret_false:
2314 return False
2315 return out
2316
2317
2318 def reset(self):
2319 """Clear ConfigObj instance and restore to 'freshly created' state."""
2320 self.clear()
2321 self._initialise()
2322 # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2323 # requires an empty dictionary
2324 self.configspec = None
2325 # Just to be sure ;-)
2326 self._original_configspec = None
2327
2328
2329 def reload(self):
2330 """
2331 Reload a ConfigObj from file.
2332
2333 This method raises a ``ReloadError`` if the ConfigObj doesn't have
2334 a filename attribute pointing to a file.
2335 """
2336 if not isinstance(self.filename, StringTypes):
2337 raise ReloadError()
2338
2339 filename = self.filename
2340 current_options = {}
2341 for entry in OPTION_DEFAULTS:
2342 if entry == 'configspec':
2343 continue
2344 current_options[entry] = getattr(self, entry)
2345
2346 configspec = self._original_configspec
2347 current_options['configspec'] = configspec
2348
2349 self.clear()
2350 self._initialise(current_options)
2351 self._load(filename, configspec)
2352
2353
2354
2355 class SimpleVal(object):
2356 """
2357 A simple validator.
2358 Can be used to check that all members expected are present.
2359
2360 To use it, provide a configspec with all your members in (the value given
2361 will be ignored). Pass an instance of ``SimpleVal`` to the ``validate``
2362 method of your ``ConfigObj``. ``validate`` will return ``True`` if all
2363 members are present, or a dictionary with True/False meaning
2364 present/missing. (Whole missing sections will be replaced with ``False``)
2365 """
2366
2367 def __init__(self):
2368 self.baseErrorClass = ConfigObjError
2369
2370 def check(self, check, member, missing=False):
2371 """A dummy check method, always returns the value unchanged."""
2372 if missing:
2373 raise self.baseErrorClass()
2374 return member
2375
2376
2377 # Check / processing functions for options
2378 def flatten_errors(cfg, res, levels=None, results=None):
2379 """
2380 An example function that will turn a nested dictionary of results
2381 (as returned by ``ConfigObj.validate``) into a flat list.
2382
2383 ``cfg`` is the ConfigObj instance being checked, ``res`` is the results
2384 dictionary returned by ``validate``.
2385
2386 (This is a recursive function, so you shouldn't use the ``levels`` or
2387 ``results`` arguments - they are used by the function.
2388
2389 Returns a list of keys that failed. Each member of the list is a tuple :
2390 ::
2391
2392 ([list of sections...], key, result)
2393
2394 If ``validate`` was called with ``preserve_errors=False`` (the default)
2395 then ``result`` will always be ``False``.
2396
2397 *list of sections* is a flattened list of sections that the key was found
2398 in.
2399
2400 If the section was missing then key will be ``None``.
2401
2402 If the value (or section) was missing then ``result`` will be ``False``.
2403
2404 If ``validate`` was called with ``preserve_errors=True`` and a value
2405 was present, but failed the check, then ``result`` will be the exception
2406 object returned. You can use this as a string that describes the failure.
2407
2408 For example *The value "3" is of the wrong type*.
2409
2410 >>> import validate
2411 >>> vtor = validate.Validator()
2412 >>> my_ini = '''
2413 ... option1 = True
2414 ... [section1]
2415 ... option1 = True
2416 ... [section2]
2417 ... another_option = Probably
2418 ... [section3]
2419 ... another_option = True
2420 ... [[section3b]]
2421 ... value = 3
2422 ... value2 = a
2423 ... value3 = 11
2424 ... '''
2425 >>> my_cfg = '''
2426 ... option1 = boolean()
2427 ... option2 = boolean()
2428 ... option3 = boolean(default=Bad_value)
2429 ... [section1]
2430 ... option1 = boolean()
2431 ... option2 = boolean()
2432 ... option3 = boolean(default=Bad_value)
2433 ... [section2]
2434 ... another_option = boolean()
2435 ... [section3]
2436 ... another_option = boolean()
2437 ... [[section3b]]
2438 ... value = integer
2439 ... value2 = integer
2440 ... value3 = integer(0, 10)
2441 ... [[[section3b-sub]]]
2442 ... value = string
2443 ... [section4]
2444 ... another_option = boolean()
2445 ... '''
2446 >>> cs = my_cfg.split('\\n')
2447 >>> ini = my_ini.split('\\n')
2448 >>> cfg = ConfigObj(ini, configspec=cs)
2449 >>> res = cfg.validate(vtor, preserve_errors=True)
2450 >>> errors = []
2451 >>> for entry in flatten_errors(cfg, res):
2452 ... section_list, key, error = entry
2453 ... section_list.insert(0, '[root]')
2454 ... if key is not None:
2455 ... section_list.append(key)
2456 ... else:
2457 ... section_list.append('[missing]')
2458 ... section_string = ', '.join(section_list)
2459 ... errors.append((section_string, ' = ', error))
2460 >>> errors.sort()
2461 >>> for entry in errors:
2462 ... print entry[0], entry[1], (entry[2] or 0)
2463 [root], option2 = 0
2464 [root], option3 = the value "Bad_value" is of the wrong type.
2465 [root], section1, option2 = 0
2466 [root], section1, option3 = the value "Bad_value" is of the wrong type.
2467 [root], section2, another_option = the value "Probably" is of the wrong type.
2468 [root], section3, section3b, section3b-sub, [missing] = 0
2469 [root], section3, section3b, value2 = the value "a" is of the wrong type.
2470 [root], section3, section3b, value3 = the value "11" is too big.
2471 [root], section4, [missing] = 0
2472 """
2473 if levels is None:
2474 # first time called
2475 levels = []
2476 results = []
2477 if res is True:
2478 return results
2479 if res is False:
2480 results.append((levels[:], None, False))
2481 if levels:
2482 levels.pop()
2483 return results
2484 for (key, val) in res.items():
2485 if val == True:
2486 continue
2487 if isinstance(cfg.get(key), dict):
2488 # Go down one level
2489 levels.append(key)
2490 flatten_errors(cfg[key], val, levels, results)
2491 continue
2492 results.append((levels[:], key, val))
2493 #
2494 # Go up one level
2495 if levels:
2496 levels.pop()
2497 #
2498 return results
2499
2500
2501 """*A programming language is a medium of expression.* - Paul Graham"""
@@ -0,0 +1,170 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3
4 # GUID.py
5 # Version 2.6
6 #
7 # Copyright (c) 2006 Conan C. Albrecht
8 #
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is furnished
14 # to do so, subject to the following conditions:
15 #
16 # The above copyright notice and this permission notice shall be included in all
17 # copies or substantial portions of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
20 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21 # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
22 # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23 # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 # DEALINGS IN THE SOFTWARE.
25
26
27
28 ##################################################################################################
29 ### A globally-unique identifier made up of time and ip and 8 digits for a counter:
30 ### each GUID is 40 characters wide
31 ###
32 ### A globally unique identifier that combines ip, time, and a counter. Since the
33 ### time is listed first, you can sort records by guid. You can also extract the time
34 ### and ip if needed.
35 ###
36 ### Since the counter has eight hex characters, you can create up to
37 ### 0xffffffff (4294967295) GUIDs every millisecond. If your processor
38 ### is somehow fast enough to create more than that in a millisecond (looking
39 ### toward the future, of course), the function will wait until the next
40 ### millisecond to return.
41 ###
42 ### GUIDs make wonderful database keys. They require no access to the
43 ### database (to get the max index number), they are extremely unique, and they sort
44 ### automatically by time. GUIDs prevent key clashes when merging
45 ### two databases together, combining data, or generating keys in distributed
46 ### systems.
47 ###
48 ### There is an Internet Draft for UUIDs, but this module does not implement it.
49 ### If the draft catches on, perhaps I'll conform the module to it.
50 ###
51
52
53 # Changelog
54 # Sometime, 1997 Created the Java version of GUID
55 # Went through many versions in Java
56 # Sometime, 2002 Created the Python version of GUID, mirroring the Java version
57 # November 24, 2003 Changed Python version to be more pythonic, took out object and made just a module
58 # December 2, 2003 Fixed duplicating GUIDs. Sometimes they duplicate if multiples are created
59 # in the same millisecond (it checks the last 100 GUIDs now and has a larger random part)
60 # December 9, 2003 Fixed MAX_RANDOM, which was going over sys.maxint
61 # June 12, 2004 Allowed a custom IP address to be sent in rather than always using the
62 # local IP address.
63 # November 4, 2005 Changed the random part to a counter variable. Now GUIDs are totally
64 # unique and more efficient, as long as they are created by only
65 # on runtime on a given machine. The counter part is after the time
66 # part so it sorts correctly.
67 # November 8, 2005 The counter variable now starts at a random long now and cycles
68 # around. This is in case two guids are created on the same
69 # machine at the same millisecond (by different processes). Even though
70 # it is possible the GUID can be created, this makes it highly unlikely
71 # since the counter will likely be different.
72 # November 11, 2005 Fixed a bug in the new IP getting algorithm. Also, use IPv6 range
73 # for IP when we make it up (when it's no accessible)
74 # November 21, 2005 Added better IP-finding code. It finds IP address better now.
75 # January 5, 2006 Fixed a small bug caused in old versions of python (random module use)
76
77 import math
78 import socket
79 import random
80 import sys
81 import time
82 import threading
83
84
85
86 #############################
87 ### global module variables
88
89 #Makes a hex IP from a decimal dot-separated ip (eg: 127.0.0.1)
90 make_hexip = lambda ip: ''.join(["%04x" % long(i) for i in ip.split('.')]) # leave space for ip v6 (65K in each sub)
91
92 MAX_COUNTER = 0xfffffffe
93 counter = 0L
94 firstcounter = MAX_COUNTER
95 lasttime = 0
96 ip = ''
97 lock = threading.RLock()
98 try: # only need to get the IP addresss once
99 ip = socket.getaddrinfo(socket.gethostname(),0)[-1][-1][0]
100 hexip = make_hexip(ip)
101 except: # if we don't have an ip, default to someting in the 10.x.x.x private range
102 ip = '10'
103 rand = random.Random()
104 for i in range(3):
105 ip += '.' + str(rand.randrange(1, 0xffff)) # might as well use IPv6 range if we're making it up
106 hexip = make_hexip(ip)
107
108
109 #################################
110 ### Public module functions
111
112 def generate(ip=None):
113 '''Generates a new guid. A guid is unique in space and time because it combines
114 the machine IP with the current time in milliseconds. Be careful about sending in
115 a specified IP address because the ip makes it unique in space. You could send in
116 the same IP address that is created on another machine.
117 '''
118 global counter, firstcounter, lasttime
119 lock.acquire() # can't generate two guids at the same time
120 try:
121 parts = []
122
123 # do we need to wait for the next millisecond (are we out of counters?)
124 now = long(time.time() * 1000)
125 while lasttime == now and counter == firstcounter:
126 time.sleep(.01)
127 now = long(time.time() * 1000)
128
129 # time part
130 parts.append("%016x" % now)
131
132 # counter part
133 if lasttime != now: # time to start counter over since we have a different millisecond
134 firstcounter = long(random.uniform(1, MAX_COUNTER)) # start at random position
135 counter = firstcounter
136 counter += 1
137 if counter > MAX_COUNTER:
138 counter = 0
139 lasttime = now
140 parts.append("%08x" % (counter))
141
142 # ip part
143 parts.append(hexip)
144
145 # put them all together
146 return ''.join(parts)
147 finally:
148 lock.release()
149
150
151 def extract_time(guid):
152 '''Extracts the time portion out of the guid and returns the
153 number of seconds since the epoch as a float'''
154 return float(long(guid[0:16], 16)) / 1000.0
155
156
157 def extract_counter(guid):
158 '''Extracts the counter from the guid (returns the bits in decimal)'''
159 return int(guid[16:24], 16)
160
161
162 def extract_ip(guid):
163 '''Extracts the ip portion out of the guid and returns it
164 as a string like 10.10.10.10'''
165 # there's probably a more elegant way to do this
166 thisip = []
167 for index in range(24, 40, 4):
168 thisip.append(str(int(guid[index: index + 4], 16)))
169 return '.'.join(thisip)
170
This diff has been collapsed as it changes many lines, (1414 lines changed) Show them Hide them
@@ -0,0 +1,1414 b''
1 # validate.py
2 # A Validator object
3 # Copyright (C) 2005 Michael Foord, Mark Andrews, Nicola Larosa
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk
5 # mark AT la-la DOT com
6 # nico AT tekNico DOT net
7
8 # This software is licensed under the terms of the BSD license.
9 # http://www.voidspace.org.uk/python/license.shtml
10 # Basically you're free to copy, modify, distribute and relicense it,
11 # So long as you keep a copy of the license with it.
12
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml
14 # For information about bugfixes, updates and support, please join the
15 # ConfigObj mailing list:
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop
17 # Comments, suggestions and bug reports welcome.
18
19 """
20 The Validator object is used to check that supplied values
21 conform to a specification.
22
23 The value can be supplied as a string - e.g. from a config file.
24 In this case the check will also *convert* the value to
25 the required type. This allows you to add validation
26 as a transparent layer to access data stored as strings.
27 The validation checks that the data is correct *and*
28 converts it to the expected type.
29
30 Some standard checks are provided for basic data types.
31 Additional checks are easy to write. They can be
32 provided when the ``Validator`` is instantiated or
33 added afterwards.
34
35 The standard functions work with the following basic data types :
36
37 * integers
38 * floats
39 * booleans
40 * strings
41 * ip_addr
42
43 plus lists of these datatypes
44
45 Adding additional checks is done through coding simple functions.
46
47 The full set of standard checks are :
48
49 * 'integer': matches integer values (including negative)
50 Takes optional 'min' and 'max' arguments : ::
51
52 integer()
53 integer(3, 9) # any value from 3 to 9
54 integer(min=0) # any positive value
55 integer(max=9)
56
57 * 'float': matches float values
58 Has the same parameters as the integer check.
59
60 * 'boolean': matches boolean values - ``True`` or ``False``
61 Acceptable string values for True are :
62 true, on, yes, 1
63 Acceptable string values for False are :
64 false, off, no, 0
65
66 Any other value raises an error.
67
68 * 'ip_addr': matches an Internet Protocol address, v.4, represented
69 by a dotted-quad string, i.e. '1.2.3.4'.
70
71 * 'string': matches any string.
72 Takes optional keyword args 'min' and 'max'
73 to specify min and max lengths of the string.
74
75 * 'list': matches any list.
76 Takes optional keyword args 'min', and 'max' to specify min and
77 max sizes of the list. (Always returns a list.)
78
79 * 'tuple': matches any tuple.
80 Takes optional keyword args 'min', and 'max' to specify min and
81 max sizes of the tuple. (Always returns a tuple.)
82
83 * 'int_list': Matches a list of integers.
84 Takes the same arguments as list.
85
86 * 'float_list': Matches a list of floats.
87 Takes the same arguments as list.
88
89 * 'bool_list': Matches a list of boolean values.
90 Takes the same arguments as list.
91
92 * 'ip_addr_list': Matches a list of IP addresses.
93 Takes the same arguments as list.
94
95 * 'string_list': Matches a list of strings.
96 Takes the same arguments as list.
97
98 * 'mixed_list': Matches a list with different types in
99 specific positions. List size must match
100 the number of arguments.
101
102 Each position can be one of :
103 'integer', 'float', 'ip_addr', 'string', 'boolean'
104
105 So to specify a list with two strings followed
106 by two integers, you write the check as : ::
107
108 mixed_list('string', 'string', 'integer', 'integer')
109
110 * 'pass': This check matches everything ! It never fails
111 and the value is unchanged.
112
113 It is also the default if no check is specified.
114
115 * 'option': This check matches any from a list of options.
116 You specify this check with : ::
117
118 option('option 1', 'option 2', 'option 3')
119
120 You can supply a default value (returned if no value is supplied)
121 using the default keyword argument.
122
123 You specify a list argument for default using a list constructor syntax in
124 the check : ::
125
126 checkname(arg1, arg2, default=list('val 1', 'val 2', 'val 3'))
127
128 A badly formatted set of arguments will raise a ``VdtParamError``.
129 """
130
131 __docformat__ = "restructuredtext en"
132
133 __version__ = '0.3.2'
134
135 __revision__ = '$Id: validate.py 123 2005-09-08 08:54:28Z fuzzyman $'
136
137 __all__ = (
138 '__version__',
139 'dottedQuadToNum',
140 'numToDottedQuad',
141 'ValidateError',
142 'VdtUnknownCheckError',
143 'VdtParamError',
144 'VdtTypeError',
145 'VdtValueError',
146 'VdtValueTooSmallError',
147 'VdtValueTooBigError',
148 'VdtValueTooShortError',
149 'VdtValueTooLongError',
150 'VdtMissingValue',
151 'Validator',
152 'is_integer',
153 'is_float',
154 'is_boolean',
155 'is_list',
156 'is_tuple',
157 'is_ip_addr',
158 'is_string',
159 'is_int_list',
160 'is_bool_list',
161 'is_float_list',
162 'is_string_list',
163 'is_ip_addr_list',
164 'is_mixed_list',
165 'is_option',
166 '__docformat__',
167 )
168
169
170 import sys
171 INTP_VER = sys.version_info[:2]
172 if INTP_VER < (2, 2):
173 raise RuntimeError("Python v.2.2 or later needed")
174
175 import re
176 StringTypes = (str, unicode)
177
178
179 _list_arg = re.compile(r'''
180 (?:
181 ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
182 (
183 (?:
184 \s*
185 (?:
186 (?:".*?")| # double quotes
187 (?:'.*?')| # single quotes
188 (?:[^'",\s\)][^,\)]*?) # unquoted
189 )
190 \s*,\s*
191 )*
192 (?:
193 (?:".*?")| # double quotes
194 (?:'.*?')| # single quotes
195 (?:[^'",\s\)][^,\)]*?) # unquoted
196 )? # last one
197 )
198 \)
199 )
200 ''', re.VERBOSE) # two groups
201
202 _list_members = re.compile(r'''
203 (
204 (?:".*?")| # double quotes
205 (?:'.*?')| # single quotes
206 (?:[^'",\s=][^,=]*?) # unquoted
207 )
208 (?:
209 (?:\s*,\s*)|(?:\s*$) # comma
210 )
211 ''', re.VERBOSE) # one group
212
213 _paramstring = r'''
214 (?:
215 (
216 (?:
217 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
218 (?:
219 \s*
220 (?:
221 (?:".*?")| # double quotes
222 (?:'.*?')| # single quotes
223 (?:[^'",\s\)][^,\)]*?) # unquoted
224 )
225 \s*,\s*
226 )*
227 (?:
228 (?:".*?")| # double quotes
229 (?:'.*?')| # single quotes
230 (?:[^'",\s\)][^,\)]*?) # unquoted
231 )? # last one
232 \)
233 )|
234 (?:
235 (?:".*?")| # double quotes
236 (?:'.*?')| # single quotes
237 (?:[^'",\s=][^,=]*?)| # unquoted
238 (?: # keyword argument
239 [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
240 (?:
241 (?:".*?")| # double quotes
242 (?:'.*?')| # single quotes
243 (?:[^'",\s=][^,=]*?) # unquoted
244 )
245 )
246 )
247 )
248 (?:
249 (?:\s*,\s*)|(?:\s*$) # comma
250 )
251 )
252 '''
253
254 _matchstring = '^%s*' % _paramstring
255
256 # Python pre 2.2.1 doesn't have bool
257 try:
258 bool
259 except NameError:
260 def bool(val):
261 """Simple boolean equivalent function. """
262 if val:
263 return 1
264 else:
265 return 0
266
267
268 def dottedQuadToNum(ip):
269 """
270 Convert decimal dotted quad string to long integer
271
272 >>> dottedQuadToNum('1 ')
273 1L
274 >>> dottedQuadToNum(' 1.2')
275 16777218L
276 >>> dottedQuadToNum(' 1.2.3 ')
277 16908291L
278 >>> dottedQuadToNum('1.2.3.4')
279 16909060L
280 >>> dottedQuadToNum('1.2.3. 4')
281 Traceback (most recent call last):
282 ValueError: Not a good dotted-quad IP: 1.2.3. 4
283 >>> dottedQuadToNum('255.255.255.255')
284 4294967295L
285 >>> dottedQuadToNum('255.255.255.256')
286 Traceback (most recent call last):
287 ValueError: Not a good dotted-quad IP: 255.255.255.256
288 """
289
290 # import here to avoid it when ip_addr values are not used
291 import socket, struct
292
293 try:
294 return struct.unpack('!L',
295 socket.inet_aton(ip.strip()))[0]
296 except socket.error:
297 # bug in inet_aton, corrected in Python 2.3
298 if ip.strip() == '255.255.255.255':
299 return 0xFFFFFFFFL
300 else:
301 raise ValueError('Not a good dotted-quad IP: %s' % ip)
302 return
303
304
305 def numToDottedQuad(num):
306 """
307 Convert long int to dotted quad string
308
309 >>> numToDottedQuad(-1L)
310 Traceback (most recent call last):
311 ValueError: Not a good numeric IP: -1
312 >>> numToDottedQuad(1L)
313 '0.0.0.1'
314 >>> numToDottedQuad(16777218L)
315 '1.0.0.2'
316 >>> numToDottedQuad(16908291L)
317 '1.2.0.3'
318 >>> numToDottedQuad(16909060L)
319 '1.2.3.4'
320 >>> numToDottedQuad(4294967295L)
321 '255.255.255.255'
322 >>> numToDottedQuad(4294967296L)
323 Traceback (most recent call last):
324 ValueError: Not a good numeric IP: 4294967296
325 """
326
327 # import here to avoid it when ip_addr values are not used
328 import socket, struct
329
330 # no need to intercept here, 4294967295L is fine
331 try:
332 return socket.inet_ntoa(
333 struct.pack('!L', long(num)))
334 except (socket.error, struct.error, OverflowError):
335 raise ValueError('Not a good numeric IP: %s' % num)
336
337
338 class ValidateError(Exception):
339 """
340 This error indicates that the check failed.
341 It can be the base class for more specific errors.
342
343 Any check function that fails ought to raise this error.
344 (or a subclass)
345
346 >>> raise ValidateError
347 Traceback (most recent call last):
348 ValidateError
349 """
350
351
352 class VdtMissingValue(ValidateError):
353 """No value was supplied to a check that needed one."""
354
355
356 class VdtUnknownCheckError(ValidateError):
357 """An unknown check function was requested"""
358
359 def __init__(self, value):
360 """
361 >>> raise VdtUnknownCheckError('yoda')
362 Traceback (most recent call last):
363 VdtUnknownCheckError: the check "yoda" is unknown.
364 """
365 ValidateError.__init__(self, 'the check "%s" is unknown.' % (value,))
366
367
368 class VdtParamError(SyntaxError):
369 """An incorrect parameter was passed"""
370
371 def __init__(self, name, value):
372 """
373 >>> raise VdtParamError('yoda', 'jedi')
374 Traceback (most recent call last):
375 VdtParamError: passed an incorrect value "jedi" for parameter "yoda".
376 """
377 SyntaxError.__init__(self, 'passed an incorrect value "%s" for parameter "%s".' % (value, name))
378
379
380 class VdtTypeError(ValidateError):
381 """The value supplied was of the wrong type"""
382
383 def __init__(self, value):
384 """
385 >>> raise VdtTypeError('jedi')
386 Traceback (most recent call last):
387 VdtTypeError: the value "jedi" is of the wrong type.
388 """
389 ValidateError.__init__(self, 'the value "%s" is of the wrong type.' % (value,))
390
391
392 class VdtValueError(ValidateError):
393 """The value supplied was of the correct type, but was not an allowed value."""
394
395 def __init__(self, value):
396 """
397 >>> raise VdtValueError('jedi')
398 Traceback (most recent call last):
399 VdtValueError: the value "jedi" is unacceptable.
400 """
401 ValidateError.__init__(self, 'the value "%s" is unacceptable.' % (value,))
402
403
404 class VdtValueTooSmallError(VdtValueError):
405 """The value supplied was of the correct type, but was too small."""
406
407 def __init__(self, value):
408 """
409 >>> raise VdtValueTooSmallError('0')
410 Traceback (most recent call last):
411 VdtValueTooSmallError: the value "0" is too small.
412 """
413 ValidateError.__init__(self, 'the value "%s" is too small.' % (value,))
414
415
416 class VdtValueTooBigError(VdtValueError):
417 """The value supplied was of the correct type, but was too big."""
418
419 def __init__(self, value):
420 """
421 >>> raise VdtValueTooBigError('1')
422 Traceback (most recent call last):
423 VdtValueTooBigError: the value "1" is too big.
424 """
425 ValidateError.__init__(self, 'the value "%s" is too big.' % (value,))
426
427
428 class VdtValueTooShortError(VdtValueError):
429 """The value supplied was of the correct type, but was too short."""
430
431 def __init__(self, value):
432 """
433 >>> raise VdtValueTooShortError('jed')
434 Traceback (most recent call last):
435 VdtValueTooShortError: the value "jed" is too short.
436 """
437 ValidateError.__init__(
438 self,
439 'the value "%s" is too short.' % (value,))
440
441
442 class VdtValueTooLongError(VdtValueError):
443 """The value supplied was of the correct type, but was too long."""
444
445 def __init__(self, value):
446 """
447 >>> raise VdtValueTooLongError('jedie')
448 Traceback (most recent call last):
449 VdtValueTooLongError: the value "jedie" is too long.
450 """
451 ValidateError.__init__(self, 'the value "%s" is too long.' % (value,))
452
453
454 class Validator(object):
455 """
456 Validator is an object that allows you to register a set of 'checks'.
457 These checks take input and test that it conforms to the check.
458
459 This can also involve converting the value from a string into
460 the correct datatype.
461
462 The ``check`` method takes an input string which configures which
463 check is to be used and applies that check to a supplied value.
464
465 An example input string would be:
466 'int_range(param1, param2)'
467
468 You would then provide something like:
469
470 >>> def int_range_check(value, min, max):
471 ... # turn min and max from strings to integers
472 ... min = int(min)
473 ... max = int(max)
474 ... # check that value is of the correct type.
475 ... # possible valid inputs are integers or strings
476 ... # that represent integers
477 ... if not isinstance(value, (int, long, StringTypes)):
478 ... raise VdtTypeError(value)
479 ... elif isinstance(value, StringTypes):
480 ... # if we are given a string
481 ... # attempt to convert to an integer
482 ... try:
483 ... value = int(value)
484 ... except ValueError:
485 ... raise VdtValueError(value)
486 ... # check the value is between our constraints
487 ... if not min <= value:
488 ... raise VdtValueTooSmallError(value)
489 ... if not value <= max:
490 ... raise VdtValueTooBigError(value)
491 ... return value
492
493 >>> fdict = {'int_range': int_range_check}
494 >>> vtr1 = Validator(fdict)
495 >>> vtr1.check('int_range(20, 40)', '30')
496 30
497 >>> vtr1.check('int_range(20, 40)', '60')
498 Traceback (most recent call last):
499 VdtValueTooBigError: the value "60" is too big.
500
501 New functions can be added with : ::
502
503 >>> vtr2 = Validator()
504 >>> vtr2.functions['int_range'] = int_range_check
505
506 Or by passing in a dictionary of functions when Validator
507 is instantiated.
508
509 Your functions *can* use keyword arguments,
510 but the first argument should always be 'value'.
511
512 If the function doesn't take additional arguments,
513 the parentheses are optional in the check.
514 It can be written with either of : ::
515
516 keyword = function_name
517 keyword = function_name()
518
519 The first program to utilise Validator() was Michael Foord's
520 ConfigObj, an alternative to ConfigParser which supports lists and
521 can validate a config file using a config schema.
522 For more details on using Validator with ConfigObj see:
523 http://www.voidspace.org.uk/python/configobj.html
524 """
525
526 # this regex does the initial parsing of the checks
527 _func_re = re.compile(r'(.+?)\((.*)\)')
528
529 # this regex takes apart keyword arguments
530 _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$')
531
532
533 # this regex finds keyword=list(....) type values
534 _list_arg = _list_arg
535
536 # this regex takes individual values out of lists - in one pass
537 _list_members = _list_members
538
539 # These regexes check a set of arguments for validity
540 # and then pull the members out
541 _paramfinder = re.compile(_paramstring, re.VERBOSE)
542 _matchfinder = re.compile(_matchstring, re.VERBOSE)
543
544
545 def __init__(self, functions=None):
546 """
547 >>> vtri = Validator()
548 """
549 self.functions = {
550 '': self._pass,
551 'integer': is_integer,
552 'float': is_float,
553 'boolean': is_boolean,
554 'ip_addr': is_ip_addr,
555 'string': is_string,
556 'list': is_list,
557 'tuple': is_tuple,
558 'int_list': is_int_list,
559 'float_list': is_float_list,
560 'bool_list': is_bool_list,
561 'ip_addr_list': is_ip_addr_list,
562 'string_list': is_string_list,
563 'mixed_list': is_mixed_list,
564 'pass': self._pass,
565 'option': is_option,
566 }
567 if functions is not None:
568 self.functions.update(functions)
569 # tekNico: for use by ConfigObj
570 self.baseErrorClass = ValidateError
571 self._cache = {}
572
573
574 def check(self, check, value, missing=False):
575 """
576 Usage: check(check, value)
577
578 Arguments:
579 check: string representing check to apply (including arguments)
580 value: object to be checked
581 Returns value, converted to correct type if necessary
582
583 If the check fails, raises a ``ValidateError`` subclass.
584
585 >>> vtor.check('yoda', '')
586 Traceback (most recent call last):
587 VdtUnknownCheckError: the check "yoda" is unknown.
588 >>> vtor.check('yoda()', '')
589 Traceback (most recent call last):
590 VdtUnknownCheckError: the check "yoda" is unknown.
591
592 >>> vtor.check('string(default="")', '', missing=True)
593 ''
594 """
595 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
596
597 if missing:
598 if default is None:
599 # no information needed here - to be handled by caller
600 raise VdtMissingValue()
601 value = self._handle_none(default)
602
603 if value is None:
604 return None
605
606 return self._check_value(value, fun_name, fun_args, fun_kwargs)
607
608
609 def _handle_none(self, value):
610 if value == 'None':
611 value = None
612 elif value in ("'None'", '"None"'):
613 # Special case a quoted None
614 value = self._unquote(value)
615 return value
616
617
618 def _parse_with_caching(self, check):
619 if check in self._cache:
620 fun_name, fun_args, fun_kwargs, default = self._cache[check]
621 # We call list and dict below to work with *copies* of the data
622 # rather than the original (which are mutable of course)
623 fun_args = list(fun_args)
624 fun_kwargs = dict(fun_kwargs)
625 else:
626 fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
627 fun_kwargs = dict((str(key), value) for (key, value) in fun_kwargs.items())
628 self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
629 return fun_name, fun_args, fun_kwargs, default
630
631
632 def _check_value(self, value, fun_name, fun_args, fun_kwargs):
633 try:
634 fun = self.functions[fun_name]
635 except KeyError:
636 raise VdtUnknownCheckError(fun_name)
637 else:
638 return fun(value, *fun_args, **fun_kwargs)
639
640
641 def _parse_check(self, check):
642 fun_match = self._func_re.match(check)
643 if fun_match:
644 fun_name = fun_match.group(1)
645 arg_string = fun_match.group(2)
646 arg_match = self._matchfinder.match(arg_string)
647 if arg_match is None:
648 # Bad syntax
649 raise VdtParamError('Bad syntax in check "%s".' % check)
650 fun_args = []
651 fun_kwargs = {}
652 # pull out args of group 2
653 for arg in self._paramfinder.findall(arg_string):
654 # args may need whitespace removing (before removing quotes)
655 arg = arg.strip()
656 listmatch = self._list_arg.match(arg)
657 if listmatch:
658 key, val = self._list_handle(listmatch)
659 fun_kwargs[key] = val
660 continue
661 keymatch = self._key_arg.match(arg)
662 if keymatch:
663 val = keymatch.group(2)
664 if not val in ("'None'", '"None"'):
665 # Special case a quoted None
666 val = self._unquote(val)
667 fun_kwargs[keymatch.group(1)] = val
668 continue
669
670 fun_args.append(self._unquote(arg))
671 else:
672 # allows for function names without (args)
673 return check, (), {}, None
674
675 # Default must be deleted if the value is specified too,
676 # otherwise the check function will get a spurious "default" keyword arg
677 try:
678 default = fun_kwargs.pop('default', None)
679 except AttributeError:
680 # Python 2.2 compatibility
681 default = None
682 try:
683 default = fun_kwargs['default']
684 del fun_kwargs['default']
685 except KeyError:
686 pass
687
688 return fun_name, fun_args, fun_kwargs, default
689
690
691 def _unquote(self, val):
692 """Unquote a value if necessary."""
693 if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
694 val = val[1:-1]
695 return val
696
697
698 def _list_handle(self, listmatch):
699 """Take apart a ``keyword=list('val, 'val')`` type string."""
700 out = []
701 name = listmatch.group(1)
702 args = listmatch.group(2)
703 for arg in self._list_members.findall(args):
704 out.append(self._unquote(arg))
705 return name, out
706
707
708 def _pass(self, value):
709 """
710 Dummy check that always passes
711
712 >>> vtor.check('', 0)
713 0
714 >>> vtor.check('', '0')
715 '0'
716 """
717 return value
718
719
720 def get_default_value(self, check):
721 """
722 Given a check, return the default value for the check
723 (converted to the right type).
724
725 If the check doesn't specify a default value then a
726 ``KeyError`` will be raised.
727 """
728 fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
729 if default is None:
730 raise KeyError('Check "%s" has no default value.' % check)
731 value = self._handle_none(default)
732 if value is None:
733 return value
734 return self._check_value(value, fun_name, fun_args, fun_kwargs)
735
736
737 def _is_num_param(names, values, to_float=False):
738 """
739 Return numbers from inputs or raise VdtParamError.
740
741 Lets ``None`` pass through.
742 Pass in keyword argument ``to_float=True`` to
743 use float for the conversion rather than int.
744
745 >>> _is_num_param(('', ''), (0, 1.0))
746 [0, 1]
747 >>> _is_num_param(('', ''), (0, 1.0), to_float=True)
748 [0.0, 1.0]
749 >>> _is_num_param(('a'), ('a'))
750 Traceback (most recent call last):
751 VdtParamError: passed an incorrect value "a" for parameter "a".
752 """
753 fun = to_float and float or int
754 out_params = []
755 for (name, val) in zip(names, values):
756 if val is None:
757 out_params.append(val)
758 elif isinstance(val, (int, long, float, StringTypes)):
759 try:
760 out_params.append(fun(val))
761 except ValueError, e:
762 raise VdtParamError(name, val)
763 else:
764 raise VdtParamError(name, val)
765 return out_params
766
767
768 # built in checks
769 # you can override these by setting the appropriate name
770 # in Validator.functions
771 # note: if the params are specified wrongly in your input string,
772 # you will also raise errors.
773
774 def is_integer(value, min=None, max=None):
775 """
776 A check that tests that a given value is an integer (int, or long)
777 and optionally, between bounds. A negative value is accepted, while
778 a float will fail.
779
780 If the value is a string, then the conversion is done - if possible.
781 Otherwise a VdtError is raised.
782
783 >>> vtor.check('integer', '-1')
784 -1
785 >>> vtor.check('integer', '0')
786 0
787 >>> vtor.check('integer', 9)
788 9
789 >>> vtor.check('integer', 'a')
790 Traceback (most recent call last):
791 VdtTypeError: the value "a" is of the wrong type.
792 >>> vtor.check('integer', '2.2')
793 Traceback (most recent call last):
794 VdtTypeError: the value "2.2" is of the wrong type.
795 >>> vtor.check('integer(10)', '20')
796 20
797 >>> vtor.check('integer(max=20)', '15')
798 15
799 >>> vtor.check('integer(10)', '9')
800 Traceback (most recent call last):
801 VdtValueTooSmallError: the value "9" is too small.
802 >>> vtor.check('integer(10)', 9)
803 Traceback (most recent call last):
804 VdtValueTooSmallError: the value "9" is too small.
805 >>> vtor.check('integer(max=20)', '35')
806 Traceback (most recent call last):
807 VdtValueTooBigError: the value "35" is too big.
808 >>> vtor.check('integer(max=20)', 35)
809 Traceback (most recent call last):
810 VdtValueTooBigError: the value "35" is too big.
811 >>> vtor.check('integer(0, 9)', False)
812 0
813 """
814 (min_val, max_val) = _is_num_param(('min', 'max'), (min, max))
815 if not isinstance(value, (int, long, StringTypes)):
816 raise VdtTypeError(value)
817 if isinstance(value, StringTypes):
818 # if it's a string - does it represent an integer ?
819 try:
820 value = int(value)
821 except ValueError:
822 raise VdtTypeError(value)
823 if (min_val is not None) and (value < min_val):
824 raise VdtValueTooSmallError(value)
825 if (max_val is not None) and (value > max_val):
826 raise VdtValueTooBigError(value)
827 return value
828
829
830 def is_float(value, min=None, max=None):
831 """
832 A check that tests that a given value is a float
833 (an integer will be accepted), and optionally - that it is between bounds.
834
835 If the value is a string, then the conversion is done - if possible.
836 Otherwise a VdtError is raised.
837
838 This can accept negative values.
839
840 >>> vtor.check('float', '2')
841 2.0
842
843 From now on we multiply the value to avoid comparing decimals
844
845 >>> vtor.check('float', '-6.8') * 10
846 -68.0
847 >>> vtor.check('float', '12.2') * 10
848 122.0
849 >>> vtor.check('float', 8.4) * 10
850 84.0
851 >>> vtor.check('float', 'a')
852 Traceback (most recent call last):
853 VdtTypeError: the value "a" is of the wrong type.
854 >>> vtor.check('float(10.1)', '10.2') * 10
855 102.0
856 >>> vtor.check('float(max=20.2)', '15.1') * 10
857 151.0
858 >>> vtor.check('float(10.0)', '9.0')
859 Traceback (most recent call last):
860 VdtValueTooSmallError: the value "9.0" is too small.
861 >>> vtor.check('float(max=20.0)', '35.0')
862 Traceback (most recent call last):
863 VdtValueTooBigError: the value "35.0" is too big.
864 """
865 (min_val, max_val) = _is_num_param(
866 ('min', 'max'), (min, max), to_float=True)
867 if not isinstance(value, (int, long, float, StringTypes)):
868 raise VdtTypeError(value)
869 if not isinstance(value, float):
870 # if it's a string - does it represent a float ?
871 try:
872 value = float(value)
873 except ValueError:
874 raise VdtTypeError(value)
875 if (min_val is not None) and (value < min_val):
876 raise VdtValueTooSmallError(value)
877 if (max_val is not None) and (value > max_val):
878 raise VdtValueTooBigError(value)
879 return value
880
881
882 bool_dict = {
883 True: True, 'on': True, '1': True, 'true': True, 'yes': True,
884 False: False, 'off': False, '0': False, 'false': False, 'no': False,
885 }
886
887
888 def is_boolean(value):
889 """
890 Check if the value represents a boolean.
891
892 >>> vtor.check('boolean', 0)
893 0
894 >>> vtor.check('boolean', False)
895 0
896 >>> vtor.check('boolean', '0')
897 0
898 >>> vtor.check('boolean', 'off')
899 0
900 >>> vtor.check('boolean', 'false')
901 0
902 >>> vtor.check('boolean', 'no')
903 0
904 >>> vtor.check('boolean', 'nO')
905 0
906 >>> vtor.check('boolean', 'NO')
907 0
908 >>> vtor.check('boolean', 1)
909 1
910 >>> vtor.check('boolean', True)
911 1
912 >>> vtor.check('boolean', '1')
913 1
914 >>> vtor.check('boolean', 'on')
915 1
916 >>> vtor.check('boolean', 'true')
917 1
918 >>> vtor.check('boolean', 'yes')
919 1
920 >>> vtor.check('boolean', 'Yes')
921 1
922 >>> vtor.check('boolean', 'YES')
923 1
924 >>> vtor.check('boolean', '')
925 Traceback (most recent call last):
926 VdtTypeError: the value "" is of the wrong type.
927 >>> vtor.check('boolean', 'up')
928 Traceback (most recent call last):
929 VdtTypeError: the value "up" is of the wrong type.
930
931 """
932 if isinstance(value, StringTypes):
933 try:
934 return bool_dict[value.lower()]
935 except KeyError:
936 raise VdtTypeError(value)
937 # we do an equality test rather than an identity test
938 # this ensures Python 2.2 compatibilty
939 # and allows 0 and 1 to represent True and False
940 if value == False:
941 return False
942 elif value == True:
943 return True
944 else:
945 raise VdtTypeError(value)
946
947
948 def is_ip_addr(value):
949 """
950 Check that the supplied value is an Internet Protocol address, v.4,
951 represented by a dotted-quad string, i.e. '1.2.3.4'.
952
953 >>> vtor.check('ip_addr', '1 ')
954 '1'
955 >>> vtor.check('ip_addr', ' 1.2')
956 '1.2'
957 >>> vtor.check('ip_addr', ' 1.2.3 ')
958 '1.2.3'
959 >>> vtor.check('ip_addr', '1.2.3.4')
960 '1.2.3.4'
961 >>> vtor.check('ip_addr', '0.0.0.0')
962 '0.0.0.0'
963 >>> vtor.check('ip_addr', '255.255.255.255')
964 '255.255.255.255'
965 >>> vtor.check('ip_addr', '255.255.255.256')
966 Traceback (most recent call last):
967 VdtValueError: the value "255.255.255.256" is unacceptable.
968 >>> vtor.check('ip_addr', '1.2.3.4.5')
969 Traceback (most recent call last):
970 VdtValueError: the value "1.2.3.4.5" is unacceptable.
971 >>> vtor.check('ip_addr', '1.2.3. 4')
972 Traceback (most recent call last):
973 VdtValueError: the value "1.2.3. 4" is unacceptable.
974 >>> vtor.check('ip_addr', 0)
975 Traceback (most recent call last):
976 VdtTypeError: the value "0" is of the wrong type.
977 """
978 if not isinstance(value, StringTypes):
979 raise VdtTypeError(value)
980 value = value.strip()
981 try:
982 dottedQuadToNum(value)
983 except ValueError:
984 raise VdtValueError(value)
985 return value
986
987
988 def is_list(value, min=None, max=None):
989 """
990 Check that the value is a list of values.
991
992 You can optionally specify the minimum and maximum number of members.
993
994 It does no check on list members.
995
996 >>> vtor.check('list', ())
997 []
998 >>> vtor.check('list', [])
999 []
1000 >>> vtor.check('list', (1, 2))
1001 [1, 2]
1002 >>> vtor.check('list', [1, 2])
1003 [1, 2]
1004 >>> vtor.check('list(3)', (1, 2))
1005 Traceback (most recent call last):
1006 VdtValueTooShortError: the value "(1, 2)" is too short.
1007 >>> vtor.check('list(max=5)', (1, 2, 3, 4, 5, 6))
1008 Traceback (most recent call last):
1009 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1010 >>> vtor.check('list(min=3, max=5)', (1, 2, 3, 4))
1011 [1, 2, 3, 4]
1012 >>> vtor.check('list', 0)
1013 Traceback (most recent call last):
1014 VdtTypeError: the value "0" is of the wrong type.
1015 >>> vtor.check('list', '12')
1016 Traceback (most recent call last):
1017 VdtTypeError: the value "12" is of the wrong type.
1018 """
1019 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1020 if isinstance(value, StringTypes):
1021 raise VdtTypeError(value)
1022 try:
1023 num_members = len(value)
1024 except TypeError:
1025 raise VdtTypeError(value)
1026 if min_len is not None and num_members < min_len:
1027 raise VdtValueTooShortError(value)
1028 if max_len is not None and num_members > max_len:
1029 raise VdtValueTooLongError(value)
1030 return list(value)
1031
1032
1033 def is_tuple(value, min=None, max=None):
1034 """
1035 Check that the value is a tuple of values.
1036
1037 You can optionally specify the minimum and maximum number of members.
1038
1039 It does no check on members.
1040
1041 >>> vtor.check('tuple', ())
1042 ()
1043 >>> vtor.check('tuple', [])
1044 ()
1045 >>> vtor.check('tuple', (1, 2))
1046 (1, 2)
1047 >>> vtor.check('tuple', [1, 2])
1048 (1, 2)
1049 >>> vtor.check('tuple(3)', (1, 2))
1050 Traceback (most recent call last):
1051 VdtValueTooShortError: the value "(1, 2)" is too short.
1052 >>> vtor.check('tuple(max=5)', (1, 2, 3, 4, 5, 6))
1053 Traceback (most recent call last):
1054 VdtValueTooLongError: the value "(1, 2, 3, 4, 5, 6)" is too long.
1055 >>> vtor.check('tuple(min=3, max=5)', (1, 2, 3, 4))
1056 (1, 2, 3, 4)
1057 >>> vtor.check('tuple', 0)
1058 Traceback (most recent call last):
1059 VdtTypeError: the value "0" is of the wrong type.
1060 >>> vtor.check('tuple', '12')
1061 Traceback (most recent call last):
1062 VdtTypeError: the value "12" is of the wrong type.
1063 """
1064 return tuple(is_list(value, min, max))
1065
1066
1067 def is_string(value, min=None, max=None):
1068 """
1069 Check that the supplied value is a string.
1070
1071 You can optionally specify the minimum and maximum number of members.
1072
1073 >>> vtor.check('string', '0')
1074 '0'
1075 >>> vtor.check('string', 0)
1076 Traceback (most recent call last):
1077 VdtTypeError: the value "0" is of the wrong type.
1078 >>> vtor.check('string(2)', '12')
1079 '12'
1080 >>> vtor.check('string(2)', '1')
1081 Traceback (most recent call last):
1082 VdtValueTooShortError: the value "1" is too short.
1083 >>> vtor.check('string(min=2, max=3)', '123')
1084 '123'
1085 >>> vtor.check('string(min=2, max=3)', '1234')
1086 Traceback (most recent call last):
1087 VdtValueTooLongError: the value "1234" is too long.
1088 """
1089 if not isinstance(value, StringTypes):
1090 raise VdtTypeError(value)
1091 (min_len, max_len) = _is_num_param(('min', 'max'), (min, max))
1092 try:
1093 num_members = len(value)
1094 except TypeError:
1095 raise VdtTypeError(value)
1096 if min_len is not None and num_members < min_len:
1097 raise VdtValueTooShortError(value)
1098 if max_len is not None and num_members > max_len:
1099 raise VdtValueTooLongError(value)
1100 return value
1101
1102
1103 def is_int_list(value, min=None, max=None):
1104 """
1105 Check that the value is a list of integers.
1106
1107 You can optionally specify the minimum and maximum number of members.
1108
1109 Each list member is checked that it is an integer.
1110
1111 >>> vtor.check('int_list', ())
1112 []
1113 >>> vtor.check('int_list', [])
1114 []
1115 >>> vtor.check('int_list', (1, 2))
1116 [1, 2]
1117 >>> vtor.check('int_list', [1, 2])
1118 [1, 2]
1119 >>> vtor.check('int_list', [1, 'a'])
1120 Traceback (most recent call last):
1121 VdtTypeError: the value "a" is of the wrong type.
1122 """
1123 return [is_integer(mem) for mem in is_list(value, min, max)]
1124
1125
1126 def is_bool_list(value, min=None, max=None):
1127 """
1128 Check that the value is a list of booleans.
1129
1130 You can optionally specify the minimum and maximum number of members.
1131
1132 Each list member is checked that it is a boolean.
1133
1134 >>> vtor.check('bool_list', ())
1135 []
1136 >>> vtor.check('bool_list', [])
1137 []
1138 >>> check_res = vtor.check('bool_list', (True, False))
1139 >>> check_res == [True, False]
1140 1
1141 >>> check_res = vtor.check('bool_list', [True, False])
1142 >>> check_res == [True, False]
1143 1
1144 >>> vtor.check('bool_list', [True, 'a'])
1145 Traceback (most recent call last):
1146 VdtTypeError: the value "a" is of the wrong type.
1147 """
1148 return [is_boolean(mem) for mem in is_list(value, min, max)]
1149
1150
1151 def is_float_list(value, min=None, max=None):
1152 """
1153 Check that the value is a list of floats.
1154
1155 You can optionally specify the minimum and maximum number of members.
1156
1157 Each list member is checked that it is a float.
1158
1159 >>> vtor.check('float_list', ())
1160 []
1161 >>> vtor.check('float_list', [])
1162 []
1163 >>> vtor.check('float_list', (1, 2.0))
1164 [1.0, 2.0]
1165 >>> vtor.check('float_list', [1, 2.0])
1166 [1.0, 2.0]
1167 >>> vtor.check('float_list', [1, 'a'])
1168 Traceback (most recent call last):
1169 VdtTypeError: the value "a" is of the wrong type.
1170 """
1171 return [is_float(mem) for mem in is_list(value, min, max)]
1172
1173
1174 def is_string_list(value, min=None, max=None):
1175 """
1176 Check that the value is a list of strings.
1177
1178 You can optionally specify the minimum and maximum number of members.
1179
1180 Each list member is checked that it is a string.
1181
1182 >>> vtor.check('string_list', ())
1183 []
1184 >>> vtor.check('string_list', [])
1185 []
1186 >>> vtor.check('string_list', ('a', 'b'))
1187 ['a', 'b']
1188 >>> vtor.check('string_list', ['a', 1])
1189 Traceback (most recent call last):
1190 VdtTypeError: the value "1" is of the wrong type.
1191 >>> vtor.check('string_list', 'hello')
1192 Traceback (most recent call last):
1193 VdtTypeError: the value "hello" is of the wrong type.
1194 """
1195 if isinstance(value, StringTypes):
1196 raise VdtTypeError(value)
1197 return [is_string(mem) for mem in is_list(value, min, max)]
1198
1199
1200 def is_ip_addr_list(value, min=None, max=None):
1201 """
1202 Check that the value is a list of IP addresses.
1203
1204 You can optionally specify the minimum and maximum number of members.
1205
1206 Each list member is checked that it is an IP address.
1207
1208 >>> vtor.check('ip_addr_list', ())
1209 []
1210 >>> vtor.check('ip_addr_list', [])
1211 []
1212 >>> vtor.check('ip_addr_list', ('1.2.3.4', '5.6.7.8'))
1213 ['1.2.3.4', '5.6.7.8']
1214 >>> vtor.check('ip_addr_list', ['a'])
1215 Traceback (most recent call last):
1216 VdtValueError: the value "a" is unacceptable.
1217 """
1218 return [is_ip_addr(mem) for mem in is_list(value, min, max)]
1219
1220
1221 fun_dict = {
1222 'integer': is_integer,
1223 'float': is_float,
1224 'ip_addr': is_ip_addr,
1225 'string': is_string,
1226 'boolean': is_boolean,
1227 }
1228
1229
1230 def is_mixed_list(value, *args):
1231 """
1232 Check that the value is a list.
1233 Allow specifying the type of each member.
1234 Work on lists of specific lengths.
1235
1236 You specify each member as a positional argument specifying type
1237
1238 Each type should be one of the following strings :
1239 'integer', 'float', 'ip_addr', 'string', 'boolean'
1240
1241 So you can specify a list of two strings, followed by
1242 two integers as :
1243
1244 mixed_list('string', 'string', 'integer', 'integer')
1245
1246 The length of the list must match the number of positional
1247 arguments you supply.
1248
1249 >>> mix_str = "mixed_list('integer', 'float', 'ip_addr', 'string', 'boolean')"
1250 >>> check_res = vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', True))
1251 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1252 1
1253 >>> check_res = vtor.check(mix_str, ('1', '2.0', '1.2.3.4', 'a', 'True'))
1254 >>> check_res == [1, 2.0, '1.2.3.4', 'a', True]
1255 1
1256 >>> vtor.check(mix_str, ('b', 2.0, '1.2.3.4', 'a', True))
1257 Traceback (most recent call last):
1258 VdtTypeError: the value "b" is of the wrong type.
1259 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a'))
1260 Traceback (most recent call last):
1261 VdtValueTooShortError: the value "(1, 2.0, '1.2.3.4', 'a')" is too short.
1262 >>> vtor.check(mix_str, (1, 2.0, '1.2.3.4', 'a', 1, 'b'))
1263 Traceback (most recent call last):
1264 VdtValueTooLongError: the value "(1, 2.0, '1.2.3.4', 'a', 1, 'b')" is too long.
1265 >>> vtor.check(mix_str, 0)
1266 Traceback (most recent call last):
1267 VdtTypeError: the value "0" is of the wrong type.
1268
1269 This test requires an elaborate setup, because of a change in error string
1270 output from the interpreter between Python 2.2 and 2.3 .
1271
1272 >>> res_seq = (
1273 ... 'passed an incorrect value "',
1274 ... 'yoda',
1275 ... '" for parameter "mixed_list".',
1276 ... )
1277 >>> if INTP_VER == (2, 2):
1278 ... res_str = "".join(res_seq)
1279 ... else:
1280 ... res_str = "'".join(res_seq)
1281 >>> try:
1282 ... vtor.check('mixed_list("yoda")', ('a'))
1283 ... except VdtParamError, err:
1284 ... str(err) == res_str
1285 1
1286 """
1287 try:
1288 length = len(value)
1289 except TypeError:
1290 raise VdtTypeError(value)
1291 if length < len(args):
1292 raise VdtValueTooShortError(value)
1293 elif length > len(args):
1294 raise VdtValueTooLongError(value)
1295 try:
1296 return [fun_dict[arg](val) for arg, val in zip(args, value)]
1297 except KeyError, e:
1298 raise VdtParamError('mixed_list', e)
1299
1300
1301 def is_option(value, *options):
1302 """
1303 This check matches the value to any of a set of options.
1304
1305 >>> vtor.check('option("yoda", "jedi")', 'yoda')
1306 'yoda'
1307 >>> vtor.check('option("yoda", "jedi")', 'jed')
1308 Traceback (most recent call last):
1309 VdtValueError: the value "jed" is unacceptable.
1310 >>> vtor.check('option("yoda", "jedi")', 0)
1311 Traceback (most recent call last):
1312 VdtTypeError: the value "0" is of the wrong type.
1313 """
1314 if not isinstance(value, StringTypes):
1315 raise VdtTypeError(value)
1316 if not value in options:
1317 raise VdtValueError(value)
1318 return value
1319
1320
1321 def _test(value, *args, **keywargs):
1322 """
1323 A function that exists for test purposes.
1324
1325 >>> checks = [
1326 ... '3, 6, min=1, max=3, test=list(a, b, c)',
1327 ... '3',
1328 ... '3, 6',
1329 ... '3,',
1330 ... 'min=1, test="a b c"',
1331 ... 'min=5, test="a, b, c"',
1332 ... 'min=1, max=3, test="a, b, c"',
1333 ... 'min=-100, test=-99',
1334 ... 'min=1, max=3',
1335 ... '3, 6, test="36"',
1336 ... '3, 6, test="a, b, c"',
1337 ... '3, max=3, test=list("a", "b", "c")',
1338 ... '''3, max=3, test=list("'a'", 'b', "x=(c)")''',
1339 ... "test='x=fish(3)'",
1340 ... ]
1341 >>> v = Validator({'test': _test})
1342 >>> for entry in checks:
1343 ... print v.check(('test(%s)' % entry), 3)
1344 (3, ('3', '6'), {'test': ['a', 'b', 'c'], 'max': '3', 'min': '1'})
1345 (3, ('3',), {})
1346 (3, ('3', '6'), {})
1347 (3, ('3',), {})
1348 (3, (), {'test': 'a b c', 'min': '1'})
1349 (3, (), {'test': 'a, b, c', 'min': '5'})
1350 (3, (), {'test': 'a, b, c', 'max': '3', 'min': '1'})
1351 (3, (), {'test': '-99', 'min': '-100'})
1352 (3, (), {'max': '3', 'min': '1'})
1353 (3, ('3', '6'), {'test': '36'})
1354 (3, ('3', '6'), {'test': 'a, b, c'})
1355 (3, ('3',), {'test': ['a', 'b', 'c'], 'max': '3'})
1356 (3, ('3',), {'test': ["'a'", 'b', 'x=(c)'], 'max': '3'})
1357 (3, (), {'test': 'x=fish(3)'})
1358
1359 >>> v = Validator()
1360 >>> v.check('integer(default=6)', '3')
1361 3
1362 >>> v.check('integer(default=6)', None, True)
1363 6
1364 >>> v.get_default_value('integer(default=6)')
1365 6
1366 >>> v.get_default_value('float(default=6)')
1367 6.0
1368 >>> v.get_default_value('pass(default=None)')
1369 >>> v.get_default_value("string(default='None')")
1370 'None'
1371 >>> v.get_default_value('pass')
1372 Traceback (most recent call last):
1373 KeyError: 'Check "pass" has no default value.'
1374 >>> v.get_default_value('pass(default=list(1, 2, 3, 4))')
1375 ['1', '2', '3', '4']
1376
1377 >>> v = Validator()
1378 >>> v.check("pass(default=None)", None, True)
1379 >>> v.check("pass(default='None')", None, True)
1380 'None'
1381 >>> v.check('pass(default="None")', None, True)
1382 'None'
1383 >>> v.check('pass(default=list(1, 2, 3, 4))', None, True)
1384 ['1', '2', '3', '4']
1385
1386 Bug test for unicode arguments
1387 >>> v = Validator()
1388 >>> v.check(u'string(min=4)', u'test')
1389 u'test'
1390
1391 >>> v = Validator()
1392 >>> v.get_default_value(u'string(min=4, default="1234")')
1393 u'1234'
1394 >>> v.check(u'string(min=4, default="1234")', u'test')
1395 u'test'
1396
1397 >>> v = Validator()
1398 >>> default = v.get_default_value('string(default=None)')
1399 >>> default == None
1400 1
1401 """
1402 return (value, args, keywargs)
1403
1404
1405 if __name__ == '__main__':
1406 # run the code tests in doctest format
1407 import doctest
1408 m = sys.modules.get('__main__')
1409 globs = m.__dict__.copy()
1410 globs.update({
1411 'INTP_VER': INTP_VER,
1412 'vtor': Validator(),
1413 })
1414 doctest.testmod(m, globs=globs)
@@ -0,0 +1,24 b''
1 # encoding: utf-8
2 """The IPython1 kernel.
3
4 The IPython kernel actually refers to three things:
5
6 * The IPython Engine
7 * The IPython Controller
8 * Clients to the IPython Controller
9
10 The kernel module implements the engine, controller and client and all the
11 network protocols needed for the various entities to talk to each other.
12
13 An end user should probably begin by looking at the `client.py` module
14 if they need blocking clients or in `asyncclient.py` if they want asynchronous,
15 deferred/Twisted using clients.
16 """
17 __docformat__ = "restructuredtext en"
18 #-------------------------------------------------------------------------------
19 # Copyright (C) 2008 The IPython Development Team
20 #
21 # Distributed under the terms of the BSD License. The full license is in
22 # the file COPYING, distributed as part of this software.
23 #-------------------------------------------------------------------------------
24 No newline at end of file
@@ -0,0 +1,41 b''
1 # encoding: utf-8
2
3 """Asynchronous clients for the IPython controller.
4
5 This module has clients for using the various interfaces of the controller
6 in a fully asynchronous manner. This means that you will need to run the
7 Twisted reactor yourself and that all methods of the client classes return
8 deferreds to the result.
9
10 The main methods are are `get_*_client` and `get_client`.
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 from IPython.kernel import codeutil
27 from IPython.kernel.clientconnector import ClientConnector
28
29 # Other things that the user will need
30 from IPython.kernel.task import Task
31 from IPython.kernel.error import CompositeError
32
33 #-------------------------------------------------------------------------------
34 # Code
35 #-------------------------------------------------------------------------------
36
37 _client_tub = ClientConnector()
38 get_multiengine_client = _client_tub.get_multiengine_client
39 get_task_client = _client_tub.get_task_client
40 get_client = _client_tub.get_client
41
@@ -0,0 +1,96 b''
1 # encoding: utf-8
2
3 """This module contains blocking clients for the controller interfaces.
4
5 Unlike the clients in `asyncclient.py`, the clients in this module are fully
6 blocking. This means that methods on the clients return the actual results
7 rather than a deferred to the result. Also, we manage the Twisted reactor
8 for you. This is done by running the reactor in a thread.
9
10 The main classes in this module are:
11
12 * MultiEngineClient
13 * TaskClient
14 * Task
15 * CompositeError
16 """
17
18 __docformat__ = "restructuredtext en"
19
20 #-------------------------------------------------------------------------------
21 # Copyright (C) 2008 The IPython Development Team
22 #
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
25 #-------------------------------------------------------------------------------
26
27 #-------------------------------------------------------------------------------
28 # Imports
29 #-------------------------------------------------------------------------------
30
31 import sys
32
33 # from IPython.tools import growl
34 # growl.start("IPython1 Client")
35
36
37 from twisted.internet import reactor
38 from IPython.kernel.clientconnector import ClientConnector
39 from IPython.kernel.twistedutil import ReactorInThread
40 from IPython.kernel.twistedutil import blockingCallFromThread
41
42 # These enable various things
43 from IPython.kernel import codeutil
44 import IPython.kernel.magic
45
46 # Other things that the user will need
47 from IPython.kernel.task import Task
48 from IPython.kernel.error import CompositeError
49
50 #-------------------------------------------------------------------------------
51 # Code
52 #-------------------------------------------------------------------------------
53
54 _client_tub = ClientConnector()
55
56
57 def get_multiengine_client(furl_or_file=''):
58 """Get the blocking MultiEngine client.
59
60 :Parameters:
61 furl_or_file : str
62 A furl or a filename containing a furl. If empty, the
63 default furl_file will be used
64
65 :Returns:
66 The connected MultiEngineClient instance
67 """
68 client = blockingCallFromThread(_client_tub.get_multiengine_client,
69 furl_or_file)
70 return client.adapt_to_blocking_client()
71
72 def get_task_client(furl_or_file=''):
73 """Get the blocking Task client.
74
75 :Parameters:
76 furl_or_file : str
77 A furl or a filename containing a furl. If empty, the
78 default furl_file will be used
79
80 :Returns:
81 The connected TaskClient instance
82 """
83 client = blockingCallFromThread(_client_tub.get_task_client,
84 furl_or_file)
85 return client.adapt_to_blocking_client()
86
87
88 MultiEngineClient = get_multiengine_client
89 TaskClient = get_task_client
90
91
92
93 # Now we start the reactor in a thread
94 rit = ReactorInThread()
95 rit.setDaemon(True)
96 rit.start() No newline at end of file
@@ -0,0 +1,150 b''
1 # encoding: utf-8
2
3 """A class for handling client connections to the controller."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from twisted.internet import defer
19
20 from IPython.kernel.fcutil import Tub, UnauthenticatedTub
21
22 from IPython.kernel.config import config_manager as kernel_config_manager
23 from IPython.config.cutils import import_item
24 from IPython.kernel.fcutil import find_furl
25
26 co = kernel_config_manager.get_config_obj()
27 client_co = co['client']
28
29 #-------------------------------------------------------------------------------
30 # The ClientConnector class
31 #-------------------------------------------------------------------------------
32
33 class ClientConnector(object):
34 """
35 This class gets remote references from furls and returns the wrapped clients.
36
37 This class is also used in `client.py` and `asyncclient.py` to create
38 a single per client-process Tub.
39 """
40
41 def __init__(self):
42 self._remote_refs = {}
43 self.tub = Tub()
44 self.tub.startService()
45
46 def get_reference(self, furl_or_file):
47 """
48 Get a remote reference using a furl or a file containing a furl.
49
50 Remote references are cached locally so once a remote reference
51 has been retrieved for a given furl, the cached version is
52 returned.
53
54 :Parameters:
55 furl_or_file : str
56 A furl or a filename containing a furl
57
58 :Returns:
59 A deferred to a remote reference
60 """
61 furl = find_furl(furl_or_file)
62 if furl in self._remote_refs:
63 d = defer.succeed(self._remote_refs[furl])
64 else:
65 d = self.tub.getReference(furl)
66 d.addCallback(self.save_ref, furl)
67 return d
68
69 def save_ref(self, ref, furl):
70 """
71 Cache a remote reference by its furl.
72 """
73 self._remote_refs[furl] = ref
74 return ref
75
76 def get_task_client(self, furl_or_file=''):
77 """
78 Get the task controller client.
79
80 This method is a simple wrapper around `get_client` that allow
81 `furl_or_file` to be empty, in which case, the furls is taken
82 from the default furl file given in the configuration.
83
84 :Parameters:
85 furl_or_file : str
86 A furl or a filename containing a furl. If empty, the
87 default furl_file will be used
88
89 :Returns:
90 A deferred to the actual client class
91 """
92 task_co = client_co['client_interfaces']['task']
93 if furl_or_file:
94 ff = furl_or_file
95 else:
96 ff = task_co['furl_file']
97 return self.get_client(ff)
98
99 def get_multiengine_client(self, furl_or_file=''):
100 """
101 Get the multiengine controller client.
102
103 This method is a simple wrapper around `get_client` that allow
104 `furl_or_file` to be empty, in which case, the furls is taken
105 from the default furl file given in the configuration.
106
107 :Parameters:
108 furl_or_file : str
109 A furl or a filename containing a furl. If empty, the
110 default furl_file will be used
111
112 :Returns:
113 A deferred to the actual client class
114 """
115 task_co = client_co['client_interfaces']['multiengine']
116 if furl_or_file:
117 ff = furl_or_file
118 else:
119 ff = task_co['furl_file']
120 return self.get_client(ff)
121
122 def get_client(self, furl_or_file):
123 """
124 Get a remote reference and wrap it in a client by furl.
125
126 This method first gets a remote reference and then calls its
127 `get_client_name` method to find the apprpriate client class
128 that should be used to wrap the remote reference.
129
130 :Parameters:
131 furl_or_file : str
132 A furl or a filename containing a furl
133
134 :Returns:
135 A deferred to the actual client class
136 """
137 furl = find_furl(furl_or_file)
138 d = self.get_reference(furl)
139 def wrap_remote_reference(rr):
140 d = rr.callRemote('get_client_name')
141 d.addCallback(lambda name: import_item(name))
142 def adapt(client_interface):
143 client = client_interface(rr)
144 client.tub = self.tub
145 return client
146 d.addCallback(adapt)
147
148 return d
149 d.addCallback(wrap_remote_reference)
150 return d
@@ -0,0 +1,32 b''
1 # encoding: utf-8
2
3 """General client interfaces."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from zope.interface import Interface, implements
19
20 class IFCClientInterfaceProvider(Interface):
21
22 def remote_get_client_name():
23 """Return a string giving the class which implements a client-side interface.
24
25 The client side of any foolscap connection initially gets a remote reference.
26 Some class is needed to adapt that reference to an interface. This...
27 """
28
29 class IBlockingClientAdaptor(Interface):
30
31 def adapt_to_blocking_client():
32 """""" No newline at end of file
@@ -0,0 +1,39 b''
1 # encoding: utf-8
2
3 """Utilities to enable code objects to be pickled.
4
5 Any process that import this module will be able to pickle code objects. This
6 includes the func_code attribute of any function. Once unpickled, new
7 functions can be built using new.function(code, globals()). Eventually
8 we need to automate all of this so that functions themselves can be pickled.
9
10 Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
11 """
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 import new, types, copy_reg
27
28 def code_ctor(*args):
29 return new.code(*args)
30
31 def reduce_code(co):
32 if co.co_freevars or co.co_cellvars:
33 raise ValueError("Sorry, cannot pickle code objects with closures")
34 return code_ctor, (co.co_argcount, co.co_nlocals, co.co_stacksize,
35 co.co_flags, co.co_code, co.co_consts, co.co_names,
36 co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
37 co.co_lnotab)
38
39 copy_reg.pickle(types.CodeType, reduce_code) No newline at end of file
@@ -0,0 +1,125 b''
1 # encoding: utf-8
2
3 """Default kernel configuration."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython.external.configobj import ConfigObj
19 from IPython.config.api import ConfigObjManager
20 from IPython.config.cutils import get_ipython_dir
21
22 default_kernel_config = ConfigObj()
23
24 try:
25 ipython_dir = get_ipython_dir() + '/'
26 except:
27 # This will defaults to the cwd
28 ipython_dir = ''
29
30 #-------------------------------------------------------------------------------
31 # Engine Configuration
32 #-------------------------------------------------------------------------------
33
34 engine_config = dict(
35 logfile = '', # Empty means log to stdout
36 furl_file = ipython_dir + 'ipcontroller-engine.furl'
37 )
38
39 #-------------------------------------------------------------------------------
40 # MPI Configuration
41 #-------------------------------------------------------------------------------
42
43 mpi_config = dict(
44 mpi4py = """from mpi4py import MPI as mpi
45 mpi.size = mpi.COMM_WORLD.Get_size()
46 mpi.rank = mpi.COMM_WORLD.Get_rank()
47 """,
48 pytrilinos = """from PyTrilinos import Epetra
49 class SimpleStruct:
50 pass
51 mpi = SimpleStruct()
52 mpi.rank = 0
53 mpi.size = 0
54 """,
55 default = ''
56 )
57
58 #-------------------------------------------------------------------------------
59 # Controller Configuration
60 #-------------------------------------------------------------------------------
61
62 controller_config = dict(
63
64 logfile = '', # Empty means log to stdout
65 import_statement = '',
66
67 engine_tub = dict(
68 ip = '', # Empty string means all interfaces
69 port = 0, # 0 means pick a port for me
70 location = '', # Empty string means try to set automatically
71 secure = True,
72 cert_file = ipython_dir + 'ipcontroller-engine.pem',
73 ),
74 engine_fc_interface = 'IPython.kernel.enginefc.IFCControllerBase',
75 engine_furl_file = ipython_dir + 'ipcontroller-engine.furl',
76
77 controller_interfaces = dict(
78 # multiengine = dict(
79 # controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
80 # fc_interface = 'IPython.kernel.multienginefc.IFCMultiEngine',
81 # furl_file = 'ipcontroller-mec.furl'
82 # ),
83 task = dict(
84 controller_interface = 'IPython.kernel.task.ITaskController',
85 fc_interface = 'IPython.kernel.taskfc.IFCTaskController',
86 furl_file = ipython_dir + 'ipcontroller-tc.furl'
87 ),
88 multiengine = dict(
89 controller_interface = 'IPython.kernel.multiengine.IMultiEngine',
90 fc_interface = 'IPython.kernel.multienginefc.IFCSynchronousMultiEngine',
91 furl_file = ipython_dir + 'ipcontroller-mec.furl'
92 )
93 ),
94
95 client_tub = dict(
96 ip = '', # Empty string means all interfaces
97 port = 0, # 0 means pick a port for me
98 location = '', # Empty string means try to set automatically
99 secure = True,
100 cert_file = ipython_dir + 'ipcontroller-client.pem'
101 )
102 )
103
104 #-------------------------------------------------------------------------------
105 # Client Configuration
106 #-------------------------------------------------------------------------------
107
108 client_config = dict(
109 client_interfaces = dict(
110 task = dict(
111 furl_file = ipython_dir + 'ipcontroller-tc.furl'
112 ),
113 multiengine = dict(
114 furl_file = ipython_dir + 'ipcontroller-mec.furl'
115 )
116 )
117 )
118
119 default_kernel_config['engine'] = engine_config
120 default_kernel_config['mpi'] = mpi_config
121 default_kernel_config['controller'] = controller_config
122 default_kernel_config['client'] = client_config
123
124
125 config_manager = ConfigObjManager(default_kernel_config, 'IPython.kernel.ini') No newline at end of file
@@ -0,0 +1,178 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_contexts -*-
3 """Context managers for IPython.
4
5 Python 2.5 introduced the `with` statement, which is based on the context
6 manager protocol. This module offers a few context managers for common cases,
7 which can also be useful as templates for writing new, application-specific
8 managers.
9 """
10
11 from __future__ import with_statement
12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
25
26 import linecache
27 import sys
28
29 from twisted.internet.error import ConnectionRefusedError
30
31 from IPython.ultraTB import _fixed_getinnerframes, findsource
32 from IPython import ipapi
33
34 from IPython.kernel import error
35
36 #---------------------------------------------------------------------------
37 # Utility functions needed by all context managers.
38 #---------------------------------------------------------------------------
39
40 def remote():
41 """Raises a special exception meant to be caught by context managers.
42 """
43 m = 'Special exception to stop local execution of parallel code.'
44 raise error.StopLocalExecution(m)
45
46
47 def strip_whitespace(source,require_remote=True):
48 """strip leading whitespace from input source.
49
50 :Parameters:
51
52 """
53 remote_mark = 'remote()'
54 # Expand tabs to avoid any confusion.
55 wsource = [l.expandtabs(4) for l in source]
56 # Detect the indentation level
57 done = False
58 for line in wsource:
59 if line.isspace():
60 continue
61 for col,char in enumerate(line):
62 if char != ' ':
63 done = True
64 break
65 if done:
66 break
67 # Now we know how much leading space there is in the code. Next, we
68 # extract up to the first line that has less indentation.
69 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
70 # detect triple quoted strings that may have flush left text.
71 for lno,line in enumerate(wsource):
72 lead = line[:col]
73 if lead.isspace():
74 continue
75 else:
76 if not lead.lstrip().startswith('#'):
77 break
78 # The real 'with' source is up to lno
79 src_lines = [l[col:] for l in wsource[:lno+1]]
80
81 # Finally, check that the source's first non-comment line begins with the
82 # special call 'remote()'
83 if require_remote:
84 for nline,line in enumerate(src_lines):
85 if line.isspace() or line.startswith('#'):
86 continue
87 if line.startswith(remote_mark):
88 break
89 else:
90 raise ValueError('%s call missing at the start of code' %
91 remote_mark)
92 out_lines = src_lines[nline+1:]
93 else:
94 # If the user specified that the remote() call wasn't mandatory
95 out_lines = src_lines
96
97 # src = ''.join(out_lines) # dbg
98 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
99 return ''.join(out_lines)
100
101 class RemoteContextBase(object):
102 def __init__(self):
103 self.ip = ipapi.get()
104
105 def _findsource_file(self,f):
106 linecache.checkcache()
107 s = findsource(f.f_code)
108 lnum = f.f_lineno
109 wsource = s[0][f.f_lineno:]
110 return strip_whitespace(wsource)
111
112 def _findsource_ipython(self,f):
113 from IPython import ipapi
114 self.ip = ipapi.get()
115 buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:]
116 wsource = [l+'\n' for l in buf ]
117
118 return strip_whitespace(wsource)
119
120 def findsource(self,frame):
121 local_ns = frame.f_locals
122 global_ns = frame.f_globals
123 if frame.f_code.co_filename == '<ipython console>':
124 src = self._findsource_ipython(frame)
125 else:
126 src = self._findsource_file(frame)
127 return src
128
129 def __enter__(self):
130 raise NotImplementedError
131
132 def __exit__ (self, etype, value, tb):
133 if issubclass(etype,error.StopLocalExecution):
134 return True
135
136 class RemoteMultiEngine(RemoteContextBase):
137 def __init__(self,mec):
138 self.mec = mec
139 RemoteContextBase.__init__(self)
140
141 def __enter__(self):
142 src = self.findsource(sys._getframe(1))
143 return self.mec.execute(src)
144
145
146 # XXX - Temporary hackish testing, we'll move this into proper tests right
147 # away
148
149 if __name__ == '__main__':
150
151 # XXX - for now, we need a running cluster to be started separately. The
152 # daemon work is almost finished, and will make much of this unnecessary.
153 from IPython.kernel import client
154 mec = client.MultiEngineClient(('127.0.0.1',10105))
155
156 try:
157 mec.get_ids()
158 except ConnectionRefusedError:
159 import os, time
160 os.system('ipcluster -n 2 &')
161 time.sleep(2)
162 mec = client.MultiEngineClient(('127.0.0.1',10105))
163
164 mec.block = False
165
166 import itertools
167 c = itertools.count()
168
169 parallel = RemoteMultiEngine(mec)
170
171 with parallel as pr:
172 # A comment
173 remote() # this means the code below only runs remotely
174 print 'Hello remote world'
175 x = 3.14
176 # Comments are OK
177 # Even misindented.
178 y = x+1
@@ -0,0 +1,376 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_controllerservice -*-
3
4 """A Twisted Service for the IPython Controller.
5
6 The IPython Controller:
7
8 * Listens for Engines to connect and then manages access to those engines.
9 * Listens for clients and passes commands from client to the Engines.
10 * Exposes an asynchronous interfaces to the Engines which themselves can block.
11 * Acts as a gateway to the Engines.
12
13 The design of the controller is somewhat abstract to allow flexibility in how
14 the controller is presented to clients. This idea is that there is a basic
15 ControllerService class that allows engines to connect to it. But, this
16 basic class has no client interfaces. To expose client interfaces developers
17 provide an adapter that makes the ControllerService look like something. For
18 example, one client interface might support task farming and another might
19 support interactive usage. The important thing is that by using interfaces
20 and adapters, a single controller can be accessed from multiple interfaces.
21 Furthermore, by adapting various client interfaces to various network
22 protocols, each client interface can be exposed to multiple network protocols.
23 See multiengine.py for an example of how to adapt the ControllerService
24 to a client interface.
25 """
26
27 __docformat__ = "restructuredtext en"
28
29 #-------------------------------------------------------------------------------
30 # Copyright (C) 2008 The IPython Development Team
31 #
32 # Distributed under the terms of the BSD License. The full license is in
33 # the file COPYING, distributed as part of this software.
34 #-------------------------------------------------------------------------------
35
36 #-------------------------------------------------------------------------------
37 # Imports
38 #-------------------------------------------------------------------------------
39
40 import os, sys
41
42 from twisted.application import service
43 from twisted.internet import defer, reactor
44 from twisted.python import log, components
45 from zope.interface import Interface, implements, Attribute
46 import zope.interface as zi
47
48 from IPython.kernel.engineservice import \
49 IEngineCore, \
50 IEngineSerialized, \
51 IEngineQueued
52
53 from IPython.config import cutils
54 from IPython.kernel import codeutil
55
56 #-------------------------------------------------------------------------------
57 # Interfaces for the Controller
58 #-------------------------------------------------------------------------------
59
60 class IControllerCore(Interface):
61 """Basic methods any controller must have.
62
63 This is basically the aspect of the controller relevant to the
64 engines and does not assume anything about how the engines will
65 be presented to a client.
66 """
67
68 engines = Attribute("A dict of engine ids and engine instances.")
69
70 def register_engine(remoteEngine, id=None, ip=None, port=None,
71 pid=None):
72 """Register new remote engine.
73
74 The controller can use the ip, port, pid of the engine to do useful things
75 like kill the engines.
76
77 :Parameters:
78 remoteEngine
79 An implementer of IEngineCore, IEngineSerialized and IEngineQueued.
80 id : int
81 Requested id.
82 ip : str
83 IP address the engine is running on.
84 port : int
85 Port the engine is on.
86 pid : int
87 pid of the running engine.
88
89 :Returns: A dict of {'id':id} and possibly other key, value pairs.
90 """
91
92 def unregister_engine(id):
93 """Handle a disconnecting engine.
94
95 :Parameters:
96 id
97 The integer engine id of the engine to unregister.
98 """
99
100 def on_register_engine_do(f, includeID, *args, **kwargs):
101 """Call ``f(*args, **kwargs)`` when an engine is registered.
102
103 :Parameters:
104 includeID : int
105 If True the first argument to f will be the id of the engine.
106 """
107
108 def on_unregister_engine_do(f, includeID, *args, **kwargs):
109 """Call ``f(*args, **kwargs)`` when an engine is unregistered.
110
111 :Parameters:
112 includeID : int
113 If True the first argument to f will be the id of the engine.
114 """
115
116 def on_register_engine_do_not(f):
117 """Stop calling f on engine registration"""
118
119 def on_unregister_engine_do_not(f):
120 """Stop calling f on engine unregistration"""
121
122 def on_n_engines_registered_do(n, f, *arg, **kwargs):
123 """Call f(*args, **kwargs) the first time the nth engine registers."""
124
125 class IControllerBase(IControllerCore):
126 """The basic controller interface."""
127 pass
128
129
130 #-------------------------------------------------------------------------------
131 # Implementation of the ControllerService
132 #-------------------------------------------------------------------------------
133
134 class ControllerService(object, service.Service):
135 """A basic Controller represented as a Twisted Service.
136
137 This class doesn't implement any client notification mechanism. That
138 is up to adapted subclasses.
139 """
140
141 # I also pick up the IService interface by inheritance from service.Service
142 implements(IControllerBase)
143 name = 'ControllerService'
144
145 def __init__(self, maxEngines=511, saveIDs=False):
146 self.saveIDs = saveIDs
147 self.engines = {}
148 self.availableIDs = range(maxEngines,-1,-1) # [511,...,0]
149 self._onRegister = []
150 self._onUnregister = []
151 self._onNRegistered = []
152
153 #---------------------------------------------------------------------------
154 # Methods used to save the engine info to a log file
155 #---------------------------------------------------------------------------
156
157 def _buildEngineInfoString(self, id, ip, port, pid):
158 if id is None:
159 id = -99
160 if ip is None:
161 ip = "-99"
162 if port is None:
163 port = -99
164 if pid is None:
165 pid = -99
166 return "Engine Info: %d %s %d %d" % (id, ip , port, pid)
167
168 def _logEngineInfo(self, id, ip, port, pid):
169 log.msg(self._buildEngineInfoString(id,ip,port,pid))
170
171 def _getEngineInfoLogFile(self):
172 # Store all logs inside the ipython directory
173 ipdir = cutils.get_ipython_dir()
174 pjoin = os.path.join
175 logdir_base = pjoin(ipdir,'log')
176 if not os.path.isdir(logdir_base):
177 os.makedirs(logdir_base)
178 logfile = os.path.join(logdir_base,'ipcontroller-%s-engine-info.log' % os.getpid())
179 return logfile
180
181 def _logEngineInfoToFile(self, id, ip, port, pid):
182 """Log info about an engine to a log file.
183
184 When an engine registers with a ControllerService, the ControllerService
185 saves information about the engine to a log file. That information
186 can be useful for various purposes, such as killing hung engines, etc.
187
188 This method takes the assigned id, ip/port and pid of the engine
189 and saves it to a file of the form:
190
191 ~/.ipython/log/ipcontroller-###-engine-info.log
192
193 where ### is the pid of the controller.
194
195 Each line of this file has the form:
196
197 Engine Info: ip ip port pid
198
199 If any of the entries are not known, they are replaced by -99.
200 """
201
202 fname = self._getEngineInfoLogFile()
203 f = open(fname, 'a')
204 s = self._buildEngineInfoString(id,ip,port,pid)
205 f.write(s + '\n')
206 f.close()
207
208 #---------------------------------------------------------------------------
209 # IControllerCore methods
210 #---------------------------------------------------------------------------
211
212 def register_engine(self, remoteEngine, id=None,
213 ip=None, port=None, pid=None):
214 """Register new engine connection"""
215
216 # What happens if these assertions fail?
217 assert IEngineCore.providedBy(remoteEngine), \
218 "engine passed to register_engine doesn't provide IEngineCore"
219 assert IEngineSerialized.providedBy(remoteEngine), \
220 "engine passed to register_engine doesn't provide IEngineSerialized"
221 assert IEngineQueued.providedBy(remoteEngine), \
222 "engine passed to register_engine doesn't provide IEngineQueued"
223 assert isinstance(id, int) or id is None, \
224 "id to register_engine must be an integer or None"
225 assert isinstance(ip, str) or ip is None, \
226 "ip to register_engine must be a string or None"
227 assert isinstance(port, int) or port is None, \
228 "port to register_engine must be an integer or None"
229 assert isinstance(pid, int) or pid is None, \
230 "pid to register_engine must be an integer or None"
231
232 desiredID = id
233 if desiredID in self.engines.keys():
234 desiredID = None
235
236 if desiredID in self.availableIDs:
237 getID = desiredID
238 self.availableIDs.remove(desiredID)
239 else:
240 getID = self.availableIDs.pop()
241 remoteEngine.id = getID
242 remoteEngine.service = self
243 self.engines[getID] = remoteEngine
244
245 # Log the Engine Information for monitoring purposes
246 self._logEngineInfoToFile(getID, ip, port, pid)
247
248 msg = "registered engine with id: %i" %getID
249 log.msg(msg)
250
251 for i in range(len(self._onRegister)):
252 (f,args,kwargs,ifid) = self._onRegister[i]
253 try:
254 if ifid:
255 f(getID, *args, **kwargs)
256 else:
257 f(*args, **kwargs)
258 except:
259 self._onRegister.pop(i)
260
261 # Call functions when the nth engine is registered and them remove them
262 for i, (n, f, args, kwargs) in enumerate(self._onNRegistered):
263 if len(self.engines.keys()) == n:
264 try:
265 try:
266 f(*args, **kwargs)
267 except:
268 log.msg("Function %r failed when the %ith engine registered" % (f, n))
269 finally:
270 self._onNRegistered.pop(i)
271
272 return {'id':getID}
273
274 def unregister_engine(self, id):
275 """Unregister engine by id."""
276
277 assert isinstance(id, int) or id is None, \
278 "id to unregister_engine must be an integer or None"
279
280 msg = "unregistered engine with id: %i" %id
281 log.msg(msg)
282 try:
283 del self.engines[id]
284 except KeyError:
285 log.msg("engine with id %i was not registered" % id)
286 else:
287 if not self.saveIDs:
288 self.availableIDs.append(id)
289 # Sort to assign lower ids first
290 self.availableIDs.sort(reverse=True)
291 else:
292 log.msg("preserving id %i" %id)
293
294 for i in range(len(self._onUnregister)):
295 (f,args,kwargs,ifid) = self._onUnregister[i]
296 try:
297 if ifid:
298 f(id, *args, **kwargs)
299 else:
300 f(*args, **kwargs)
301 except:
302 self._onUnregister.pop(i)
303
304 def on_register_engine_do(self, f, includeID, *args, **kwargs):
305 assert callable(f), "f must be callable"
306 self._onRegister.append((f,args,kwargs,includeID))
307
308 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
309 assert callable(f), "f must be callable"
310 self._onUnregister.append((f,args,kwargs,includeID))
311
312 def on_register_engine_do_not(self, f):
313 for i in range(len(self._onRegister)):
314 g = self._onRegister[i][0]
315 if f == g:
316 self._onRegister.pop(i)
317 return
318
319 def on_unregister_engine_do_not(self, f):
320 for i in range(len(self._onUnregister)):
321 g = self._onUnregister[i][0]
322 if f == g:
323 self._onUnregister.pop(i)
324 return
325
326 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
327 if len(self.engines.keys()) >= n:
328 f(*args, **kwargs)
329 else:
330 self._onNRegistered.append((n,f,args,kwargs))
331
332
333 #-------------------------------------------------------------------------------
334 # Base class for adapting controller to different client APIs
335 #-------------------------------------------------------------------------------
336
337 class ControllerAdapterBase(object):
338 """All Controller adapters should inherit from this class.
339
340 This class provides a wrapped version of the IControllerBase interface that
341 can be used to easily create new custom controllers. Subclasses of this
342 will provide a full implementation of IControllerBase.
343
344 This class doesn't implement any client notification mechanism. That
345 is up to subclasses.
346 """
347
348 implements(IControllerBase)
349
350 def __init__(self, controller):
351 self.controller = controller
352 # Needed for IControllerCore
353 self.engines = self.controller.engines
354
355 def register_engine(self, remoteEngine, id=None,
356 ip=None, port=None, pid=None):
357 return self.controller.register_engine(remoteEngine,
358 id, ip, port, pid)
359
360 def unregister_engine(self, id):
361 return self.controller.unregister_engine(id)
362
363 def on_register_engine_do(self, f, includeID, *args, **kwargs):
364 return self.controller.on_register_engine_do(f, includeID, *args, **kwargs)
365
366 def on_unregister_engine_do(self, f, includeID, *args, **kwargs):
367 return self.controller.on_unregister_engine_do(f, includeID, *args, **kwargs)
368
369 def on_register_engine_do_not(self, f):
370 return self.controller.on_register_engine_do_not(f)
371
372 def on_unregister_engine_do_not(self, f):
373 return self.controller.on_unregister_engine_do_not(f)
374
375 def on_n_engines_registered_do(self, n, f, *args, **kwargs):
376 return self.controller.on_n_engines_registered_do(n, f, *args, **kwargs)
@@ -0,0 +1,16 b''
1 # encoding: utf-8
2
3 """The IPython Core."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,25 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 from IPython.external.configobj import ConfigObj
17 from IPython.config.api import ConfigObjManager
18
19 default_core_config = ConfigObj()
20 default_core_config['shell'] = dict(
21 shell_class = 'IPython.kernel.core.interpreter.Interpreter',
22 import_statement = ''
23 )
24
25 config_manager = ConfigObjManager(default_core_config, 'IPython.kernel.core.ini') No newline at end of file
@@ -0,0 +1,70 b''
1 # encoding: utf-8
2
3 """Objects for replacing sys.displayhook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 class IDisplayFormatter(object):
19 """ Objects conforming to this interface will be responsible for formatting
20 representations of objects that pass through sys.displayhook() during an
21 interactive interpreter session.
22 """
23
24 # The kind of formatter.
25 kind = 'display'
26
27 # The unique identifier for this formatter.
28 identifier = None
29
30
31 def __call__(self, obj):
32 """ Return a formatted representation of an object.
33
34 Return None if one cannot return a representation in this format.
35 """
36
37 raise NotImplementedError
38
39
40 class ReprDisplayFormatter(IDisplayFormatter):
41 """ Return the repr() string representation of an object.
42 """
43
44 # The unique identifier for this formatter.
45 identifier = 'repr'
46
47
48 def __call__(self, obj):
49 """ Return a formatted representation of an object.
50 """
51
52 return repr(obj)
53
54
55 class PPrintDisplayFormatter(IDisplayFormatter):
56 """ Return a pretty-printed string representation of an object.
57 """
58
59 # The unique identifier for this formatter.
60 identifier = 'pprint'
61
62
63 def __call__(self, obj):
64 """ Return a formatted representation of an object.
65 """
66
67 import pprint
68 return pprint.pformat(obj)
69
70
@@ -0,0 +1,100 b''
1 # encoding: utf-8
2
3 """Manager for replacing sys.displayhook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 # Standard library imports.
19 import sys
20
21
22
23 class DisplayTrap(object):
24 """ Object to trap and format objects passing through sys.displayhook().
25
26 This trap maintains two lists of callables: formatters and callbacks. The
27 formatters take the *last* object that has gone through since the trap was
28 set and returns a string representation. Callbacks are executed on *every*
29 object that passes through the displayhook and does not return anything.
30 """
31
32 def __init__(self, formatters=None, callbacks=None):
33 # A list of formatters to apply. Each should be an instance conforming
34 # to the IDisplayFormatter interface.
35 if formatters is None:
36 formatters = []
37 self.formatters = formatters
38
39 # A list of callables, each of which should be executed *every* time an
40 # object passes through sys.displayhook().
41 if callbacks is None:
42 callbacks = []
43 self.callbacks = callbacks
44
45 # The last object to pass through the displayhook.
46 self.obj = None
47
48 # The previous hook before we replace it.
49 self.old_hook = None
50
51 def hook(self, obj):
52 """ This method actually implements the hook.
53 """
54
55 # Run through the list of callbacks and trigger all of them.
56 for callback in self.callbacks:
57 callback(obj)
58
59 # Store the object for formatting.
60 self.obj = obj
61
62 def set(self):
63 """ Set the hook.
64 """
65
66 if sys.displayhook is not self.hook:
67 self.old_hook = sys.displayhook
68 sys.displayhook = self.hook
69
70 def unset(self):
71 """ Unset the hook.
72 """
73
74 sys.displayhook = self.old_hook
75
76 def clear(self):
77 """ Reset the stored object.
78 """
79
80 self.obj = None
81
82 def add_to_message(self, message):
83 """ Add the formatted display of the objects to the message dictionary
84 being returned from the interpreter to its listeners.
85 """
86
87 # If there was no displayed object (or simply None), then don't add
88 # anything.
89 if self.obj is None:
90 return
91
92 # Go through the list of formatters and let them add their formatting.
93 display = {}
94 for formatter in self.formatters:
95 representation = formatter(self.obj)
96 if representation is not None:
97 display[formatter.identifier] = representation
98
99 message['display'] = display
100
@@ -0,0 +1,41 b''
1 # encoding: utf-8
2
3 """
4 error.py
5
6 We declare here a class hierarchy for all exceptions produced by IPython, in
7 cases where we don't just raise one from the standard library.
8 """
9
10 __docformat__ = "restructuredtext en"
11
12 #-------------------------------------------------------------------------------
13 # Copyright (C) 2008 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-------------------------------------------------------------------------------
18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22
23
24 class IPythonError(Exception):
25 """Base exception that all of our exceptions inherit from.
26
27 This can be raised by code that doesn't have any more specific
28 information."""
29
30 pass
31
32 # Exceptions associated with the controller objects
33 class ControllerError(IPythonError): pass
34
35 class ControllerCreationError(ControllerError): pass
36
37
38 # Exceptions associated with the Engines
39 class EngineError(IPythonError): pass
40
41 class EngineCreationError(EngineError): pass
@@ -0,0 +1,137 b''
1 # encoding: utf-8
2
3 """ Manage the input and output history of the interpreter and the
4 frontend.
5
6 There are 2 different history objects, one that lives in the interpreter,
7 and one that lives in the frontend. They are synced with a diff at each
8 execution of a command, as the interpreter history is a real stack, its
9 existing entries are not mutable.
10 """
11
12 __docformat__ = "restructuredtext en"
13
14 #-------------------------------------------------------------------------------
15 # Copyright (C) 2008 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-------------------------------------------------------------------------------
20
21 #-------------------------------------------------------------------------------
22 # Imports
23 #-------------------------------------------------------------------------------
24
25 from copy import copy
26
27 # Local imports.
28 from util import InputList
29
30
31 ##############################################################################
32 class History(object):
33 """ An object managing the input and output history.
34 """
35
36 def __init__(self, input_cache=None, output_cache=None):
37
38 # Stuff that IPython adds to the namespace.
39 self.namespace_additions = dict(
40 _ = None,
41 __ = None,
42 ___ = None,
43 )
44
45 # A list to store input commands.
46 if input_cache is None:
47 input_cache =InputList([])
48 self.input_cache = input_cache
49
50 # A dictionary to store trapped output.
51 if output_cache is None:
52 output_cache = {}
53 self.output_cache = output_cache
54
55 def get_history_item(self, index):
56 """ Returns the history string at index, where index is the
57 distance from the end (positive).
58 """
59 if index>0 and index<len(self.input_cache):
60 return self.input_cache[index]
61
62
63 ##############################################################################
64 class InterpreterHistory(History):
65 """ An object managing the input and output history at the interpreter
66 level.
67 """
68
69 def setup_namespace(self, namespace):
70 """ Add the input and output caches into the interpreter's namespace
71 with IPython-conventional names.
72
73 Parameters
74 ----------
75 namespace : dict
76 """
77
78 namespace['In'] = self.input_cache
79 namespace['_ih'] = self.input_cache
80 namespace['Out'] = self.output_cache
81 namespace['_oh'] = self.output_cache
82
83 def update_history(self, interpreter, python):
84 """ Update the history objects that this object maintains and the
85 interpreter's namespace.
86
87 Parameters
88 ----------
89 interpreter : Interpreter
90 python : str
91 The real Python code that was translated and actually executed.
92 """
93
94 number = interpreter.current_cell_number
95
96 new_obj = interpreter.display_trap.obj
97 if new_obj is not None:
98 self.namespace_additions['___'] = self.namespace_additions['__']
99 self.namespace_additions['__'] = self.namespace_additions['_']
100 self.namespace_additions['_'] = new_obj
101 self.output_cache[number] = new_obj
102
103 interpreter.user_ns.update(self.namespace_additions)
104 self.input_cache.add(number, python)
105
106
107 def get_history_item(self, index):
108 """ Returns the history string at index, where index is the
109 distance from the end (positive).
110 """
111 if index>0 and index<(len(self.input_cache)-1):
112 return self.input_cache[-index]
113
114 def get_input_cache(self):
115 return copy(self.input_cache)
116
117 def get_input_after(self, index):
118 """ Returns the list of the commands entered after index.
119 """
120 # We need to call directly list.__getslice__, because this object
121 # is not a real list.
122 return list.__getslice__(self.input_cache, index,
123 len(self.input_cache))
124
125
126 ##############################################################################
127 class FrontEndHistory(History):
128 """ An object managing the input and output history at the frontend.
129 It is used as a local cache to reduce network latency problems
130 and multiple users editing the same thing.
131 """
132
133 def add_items(self, item_list):
134 """ Adds the given command list to the stack of executed
135 commands.
136 """
137 self.input_cache.extend(item_list)
This diff has been collapsed as it changes many lines, (749 lines changed) Show them Hide them
@@ -0,0 +1,749 b''
1 # encoding: utf-8
2
3 """Central interpreter object for an IPython engine.
4
5 The interpreter is the object whose job is to process lines of user input and
6 actually execute them in the user's namespace.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 # Standard library imports.
23 from compiler.ast import Discard
24 from types import FunctionType
25
26 import __builtin__
27 import codeop
28 import compiler
29 import pprint
30 import sys
31 import traceback
32
33 # Local imports.
34 from IPython.kernel.core import ultraTB
35 from IPython.kernel.core.display_trap import DisplayTrap
36 from IPython.kernel.core.macro import Macro
37 from IPython.kernel.core.prompts import CachedOutput
38 from IPython.kernel.core.traceback_trap import TracebackTrap
39 from IPython.kernel.core.util import Bunch, system_shell
40 from IPython.external.Itpl import ItplNS
41
42 # Global constants
43 COMPILER_ERROR = 'error'
44 INCOMPLETE_INPUT = 'incomplete'
45 COMPLETE_INPUT = 'complete'
46
47 ##############################################################################
48 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
49 # not
50
51 rc = Bunch()
52 rc.cache_size = 100
53 rc.pprint = True
54 rc.separate_in = '\n'
55 rc.separate_out = '\n'
56 rc.separate_out2 = ''
57 rc.prompt_in1 = r'In [\#]: '
58 rc.prompt_in2 = r' .\\D.: '
59 rc.prompt_out = ''
60 rc.prompts_pad_left = False
61
62 ##############################################################################
63
64 # Top-level utilities
65 def default_display_formatters():
66 """ Return a list of default display formatters.
67 """
68
69 from display_formatter import PPrintDisplayFormatter, ReprDisplayFormatter
70 return [PPrintDisplayFormatter(), ReprDisplayFormatter()]
71
72 def default_traceback_formatters():
73 """ Return a list of default traceback formatters.
74 """
75
76 from traceback_formatter import PlainTracebackFormatter
77 return [PlainTracebackFormatter()]
78
79 # Top-level classes
80 class NotDefined(object): pass
81
82 class Interpreter(object):
83 """ An interpreter object.
84
85 fixme: needs to negotiate available formatters with frontends.
86
87 Important: the interpeter should be built so that it exposes a method
88 for each attribute/method of its sub-object. This way it can be
89 replaced by a network adapter.
90 """
91
92 def __init__(self, user_ns=None, global_ns=None,translator=None,
93 magic=None, display_formatters=None,
94 traceback_formatters=None, output_trap=None, history=None,
95 message_cache=None, filename='<string>', config=None):
96
97 # The local/global namespaces for code execution
98 local_ns = user_ns # compatibility name
99 if local_ns is None:
100 local_ns = {}
101 self.user_ns = local_ns
102 # The local namespace
103 if global_ns is None:
104 global_ns = {}
105 self.user_global_ns = global_ns
106
107 # An object that will translate commands into executable Python.
108 # The current translator does not work properly so for now we are going
109 # without!
110 # if translator is None:
111 # from IPython.kernel.core.translator import IPythonTranslator
112 # translator = IPythonTranslator()
113 self.translator = translator
114
115 # An object that maintains magic commands.
116 if magic is None:
117 from IPython.kernel.core.magic import Magic
118 magic = Magic(self)
119 self.magic = magic
120
121 # A list of formatters for the displayhook.
122 if display_formatters is None:
123 display_formatters = default_display_formatters()
124 self.display_formatters = display_formatters
125
126 # A list of formatters for tracebacks.
127 if traceback_formatters is None:
128 traceback_formatters = default_traceback_formatters()
129 self.traceback_formatters = traceback_formatters
130
131 # The object trapping stdout/stderr.
132 if output_trap is None:
133 from IPython.kernel.core.output_trap import OutputTrap
134 output_trap = OutputTrap()
135 self.output_trap = output_trap
136
137 # An object that manages the history.
138 if history is None:
139 from IPython.kernel.core.history import InterpreterHistory
140 history = InterpreterHistory()
141 self.history = history
142 self.get_history_item = history.get_history_item
143 self.get_history_input_cache = history.get_input_cache
144 self.get_history_input_after = history.get_input_after
145
146 # An object that caches all of the return messages.
147 if message_cache is None:
148 from IPython.kernel.core.message_cache import SimpleMessageCache
149 message_cache = SimpleMessageCache()
150 self.message_cache = message_cache
151
152 # The "filename" of the code that is executed in this interpreter.
153 self.filename = filename
154
155 # An object that contains much configuration information.
156 if config is None:
157 # fixme: Move this constant elsewhere!
158 config = Bunch(ESC_MAGIC='%')
159 self.config = config
160
161 # Hook managers.
162 # fixme: make the display callbacks configurable. In the meantime,
163 # enable macros.
164 self.display_trap = DisplayTrap(
165 formatters=self.display_formatters,
166 callbacks=[self._possible_macro],
167 )
168 self.traceback_trap = TracebackTrap(
169 formatters=self.traceback_formatters)
170
171 # This is used temporarily for reformating exceptions in certain
172 # cases. It will go away once the ultraTB stuff is ported
173 # to ipython1
174 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
175 mode='Context',
176 tb_offset=2)
177
178 # An object that can compile commands and remember __future__
179 # statements.
180 self.command_compiler = codeop.CommandCompiler()
181
182 # A replacement for the raw_input() and input() builtins. Change these
183 # attributes later to configure them.
184 self.raw_input_builtin = raw_input
185 self.input_builtin = input
186
187 # The number of the current cell.
188 self.current_cell_number = 1
189
190 # Initialize cache, set in/out prompts and printing system
191 self.outputcache = CachedOutput(self,
192 rc.cache_size,
193 rc.pprint,
194 input_sep = rc.separate_in,
195 output_sep = rc.separate_out,
196 output_sep2 = rc.separate_out2,
197 ps1 = rc.prompt_in1,
198 ps2 = rc.prompt_in2,
199 ps_out = rc.prompt_out,
200 pad_left = rc.prompts_pad_left)
201
202 # Need to decide later if this is the right approach, but clients
203 # commonly use sys.ps1/2, so it may be best to just set them here
204 sys.ps1 = self.outputcache.prompt1.p_str
205 sys.ps2 = self.outputcache.prompt2.p_str
206
207 # This is the message dictionary assigned temporarily when running the
208 # code.
209 self.message = None
210
211 self.setup_namespace()
212
213
214 #### Public 'Interpreter' interface ########################################
215
216 def formatTraceback(self, et, ev, tb, message=''):
217 """Put a formatted version of the traceback into value and reraise.
218
219 When exceptions have to be sent over the network, the traceback
220 needs to be put into the value of the exception in a nicely
221 formatted way. The method takes the type, value and tb of an
222 exception and puts a string representation of the tb into the
223 value of the exception and reraises it.
224
225 Currently this method uses the ultraTb formatter from IPython trunk.
226 Eventually it should simply use the traceback formatters in core
227 that are loaded into self.tracback_trap.formatters.
228 """
229 tbinfo = self.tbHandler.text(et,ev,tb)
230 ev._ipython_traceback_text = tbinfo
231 return et, ev, tb
232
233 def execute(self, commands, raiseException=True):
234 """ Execute some IPython commands.
235
236 1. Translate them into Python.
237 2. Run them.
238 3. Trap stdout/stderr.
239 4. Trap sys.displayhook().
240 5. Trap exceptions.
241 6. Return a message object.
242
243 Parameters
244 ----------
245 commands : str
246 The raw commands that the user typed into the prompt.
247
248 Returns
249 -------
250 message : dict
251 The dictionary of responses. See the README.txt in this directory
252 for an explanation of the format.
253 """
254
255 # Create a message dictionary with all of the information we will be
256 # returning to the frontend and other listeners.
257 message = self.setup_message()
258
259 # Massage the input and store the raw and translated commands into
260 # a dict.
261 user_input = dict(raw=commands)
262 if self.translator is not None:
263 python = self.translator(commands, message)
264 if python is None:
265 # Something went wrong with the translation. The translator
266 # should have added an appropriate entry to the message object.
267 return message
268 else:
269 python = commands
270 user_input['translated'] = python
271 message['input'] = user_input
272
273 # Set the message object so that any magics executed in the code have
274 # access.
275 self.message = message
276
277 # Set all of the output/exception traps.
278 self.set_traps()
279
280 # Actually execute the Python code.
281 status = self.execute_python(python)
282
283 # Unset all of the traps.
284 self.unset_traps()
285
286 # Unset the message object.
287 self.message = None
288
289 # Update the history variables in the namespace.
290 # E.g. In, Out, _, __, ___
291 if self.history is not None:
292 self.history.update_history(self, python)
293
294 # Let all of the traps contribute to the message and then clear their
295 # stored information.
296 self.output_trap.add_to_message(message)
297 self.output_trap.clear()
298 self.display_trap.add_to_message(message)
299 self.display_trap.clear()
300 self.traceback_trap.add_to_message(message)
301 # Pull out the type, value and tb of the current exception
302 # before clearing it.
303 einfo = self.traceback_trap.args
304 self.traceback_trap.clear()
305
306 # Cache the message.
307 self.message_cache.add_message(self.current_cell_number, message)
308
309 # Bump the number.
310 self.current_cell_number += 1
311
312 # This conditional lets the execute method either raise any
313 # exception that has occured in user code OR return the message
314 # dict containing the traceback and other useful info.
315 if raiseException and einfo:
316 raise einfo[0],einfo[1],einfo[2]
317 else:
318 return message
319
320 def generate_prompt(self, is_continuation):
321 """Calculate and return a string with the prompt to display.
322
323 :Parameters:
324 is_continuation : bool
325 Whether the input line is continuing multiline input or not, so
326 that a proper continuation prompt can be computed."""
327
328 if is_continuation:
329 return str(self.outputcache.prompt2)
330 else:
331 return str(self.outputcache.prompt1)
332
333 def execute_python(self, python):
334 """ Actually run the Python code in the namespace.
335
336 :Parameters:
337
338 python : str
339 Pure, exec'able Python code. Special IPython commands should have
340 already been translated into pure Python.
341 """
342
343 # We use a CommandCompiler instance to compile the code so as to keep
344 # track of __future__ imports.
345 try:
346 commands = self.split_commands(python)
347 except (SyntaxError, IndentationError), e:
348 # Save the exc_info so compilation related exceptions can be
349 # reraised
350 self.traceback_trap.args = sys.exc_info()
351 self.pack_exception(self.message,e)
352 return None
353
354 for cmd in commands:
355 try:
356 code = self.command_compiler(cmd, self.filename, 'single')
357 except (SyntaxError, OverflowError, ValueError), e:
358 self.traceback_trap.args = sys.exc_info()
359 self.pack_exception(self.message,e)
360 # No point in continuing if one block raised
361 return None
362 else:
363 self.execute_block(code)
364
365 def execute_block(self,code):
366 """Execute a single block of code in the user namespace.
367
368 Return value: a flag indicating whether the code to be run completed
369 successfully:
370
371 - 0: successful execution.
372 - 1: an error occurred.
373 """
374
375 outflag = 1 # start by assuming error, success will reset it
376 try:
377 exec code in self.user_ns
378 outflag = 0
379 except SystemExit:
380 self.resetbuffer()
381 self.traceback_trap.args = sys.exc_info()
382 except:
383 self.traceback_trap.args = sys.exc_info()
384
385 return outflag
386
387 def execute_macro(self, macro):
388 """ Execute the value of a macro.
389
390 Parameters
391 ----------
392 macro : Macro
393 """
394
395 python = macro.value
396 if self.translator is not None:
397 python = self.translator(python)
398 self.execute_python(python)
399
400 def getCommand(self, i=None):
401 """Gets the ith message in the message_cache.
402
403 This is implemented here for compatibility with the old ipython1 shell
404 I am not sure we need this though. I even seem to remember that we
405 were going to get rid of it.
406 """
407 return self.message_cache.get_message(i)
408
409 def reset(self):
410 """Reset the interpreter.
411
412 Currently this only resets the users variables in the namespace.
413 In the future we might want to also reset the other stateful
414 things like that the Interpreter has, like In, Out, etc.
415 """
416 self.user_ns.clear()
417 self.setup_namespace()
418
419 def complete(self,line,text=None, pos=None):
420 """Complete the given text.
421
422 :Parameters:
423
424 text : str
425 Text fragment to be completed on. Typically this is
426 """
427 # fixme: implement
428 raise NotImplementedError
429
430 def push(self, ns):
431 """ Put value into the namespace with name key.
432
433 Parameters
434 ----------
435 **kwds
436 """
437
438 self.user_ns.update(ns)
439
440 def push_function(self, ns):
441 # First set the func_globals for all functions to self.user_ns
442 new_kwds = {}
443 for k, v in ns.iteritems():
444 if not isinstance(v, FunctionType):
445 raise TypeError("function object expected")
446 new_kwds[k] = FunctionType(v.func_code, self.user_ns)
447 self.user_ns.update(new_kwds)
448
449 def pack_exception(self,message,exc):
450 message['exception'] = exc.__class__
451 message['exception_value'] = \
452 traceback.format_exception_only(exc.__class__, exc)
453
454 def feed_block(self, source, filename='<input>', symbol='single'):
455 """Compile some source in the interpreter.
456
457 One several things can happen:
458
459 1) The input is incorrect; compile_command() raised an
460 exception (SyntaxError or OverflowError).
461
462 2) The input is incomplete, and more input is required;
463 compile_command() returned None. Nothing happens.
464
465 3) The input is complete; compile_command() returned a code
466 object. The code is executed by calling self.runcode() (which
467 also handles run-time exceptions, except for SystemExit).
468
469 The return value is:
470
471 - True in case 2
472
473 - False in the other cases, unless an exception is raised, where
474 None is returned instead. This can be used by external callers to
475 know whether to continue feeding input or not.
476
477 The return value can be used to decide whether to use sys.ps1 or
478 sys.ps2 to prompt the next line."""
479
480 self.message = self.setup_message()
481
482 try:
483 code = self.command_compiler(source,filename,symbol)
484 except (OverflowError, SyntaxError, IndentationError, ValueError ), e:
485 # Case 1
486 self.traceback_trap.args = sys.exc_info()
487 self.pack_exception(self.message,e)
488 return COMPILER_ERROR,False
489
490 if code is None:
491 # Case 2: incomplete input. This means that the input can span
492 # multiple lines. But we still need to decide when to actually
493 # stop taking user input. Later we'll add auto-indentation support
494 # somehow. In the meantime, we'll just stop if there are two lines
495 # of pure whitespace at the end.
496 last_two = source.rsplit('\n',2)[-2:]
497 print 'last two:',last_two # dbg
498 if len(last_two)==2 and all(s.isspace() for s in last_two):
499 return COMPLETE_INPUT,False
500 else:
501 return INCOMPLETE_INPUT, True
502 else:
503 # Case 3
504 return COMPLETE_INPUT, False
505
506 def pull(self, keys):
507 """ Get an item out of the namespace by key.
508
509 Parameters
510 ----------
511 key : str
512
513 Returns
514 -------
515 value : object
516
517 Raises
518 ------
519 TypeError if the key is not a string.
520 NameError if the object doesn't exist.
521 """
522
523 if isinstance(keys, str):
524 result = self.user_ns.get(keys, NotDefined())
525 if isinstance(result, NotDefined):
526 raise NameError('name %s is not defined' % keys)
527 elif isinstance(keys, (list, tuple)):
528 result = []
529 for key in keys:
530 if not isinstance(key, str):
531 raise TypeError("objects must be keyed by strings.")
532 else:
533 r = self.user_ns.get(key, NotDefined())
534 if isinstance(r, NotDefined):
535 raise NameError('name %s is not defined' % key)
536 else:
537 result.append(r)
538 if len(keys)==1:
539 result = result[0]
540 else:
541 raise TypeError("keys must be a strong or a list/tuple of strings")
542 return result
543
544 def pull_function(self, keys):
545 return self.pull(keys)
546
547 #### Interactive user API ##################################################
548
549 def ipsystem(self, command):
550 """ Execute a command in a system shell while expanding variables in the
551 current namespace.
552
553 Parameters
554 ----------
555 command : str
556 """
557
558 # Expand $variables.
559 command = self.var_expand(command)
560
561 system_shell(command,
562 header='IPython system call: ',
563 verbose=self.rc.system_verbose,
564 )
565
566 def ipmagic(self, arg_string):
567 """ Call a magic function by name.
568
569 ipmagic('name -opt foo bar') is equivalent to typing at the ipython
570 prompt:
571
572 In[1]: %name -opt foo bar
573
574 To call a magic without arguments, simply use ipmagic('name').
575
576 This provides a proper Python function to call IPython's magics in any
577 valid Python code you can type at the interpreter, including loops and
578 compound statements. It is added by IPython to the Python builtin
579 namespace upon initialization.
580
581 Parameters
582 ----------
583 arg_string : str
584 A string containing the name of the magic function to call and any
585 additional arguments to be passed to the magic.
586
587 Returns
588 -------
589 something : object
590 The return value of the actual object.
591 """
592
593 # Taken from IPython.
594 raise NotImplementedError('Not ported yet')
595
596 args = arg_string.split(' ', 1)
597 magic_name = args[0]
598 magic_name = magic_name.lstrip(self.config.ESC_MAGIC)
599
600 try:
601 magic_args = args[1]
602 except IndexError:
603 magic_args = ''
604 fn = getattr(self.magic, 'magic_'+magic_name, None)
605 if fn is None:
606 self.error("Magic function `%s` not found." % magic_name)
607 else:
608 magic_args = self.var_expand(magic_args)
609 return fn(magic_args)
610
611
612 #### Private 'Interpreter' interface #######################################
613
614 def setup_message(self):
615 """Return a message object.
616
617 This method prepares and returns a message dictionary. This dict
618 contains the various fields that are used to transfer information about
619 execution, results, tracebacks, etc, to clients (either in or out of
620 process ones). Because of the need to work with possibly out of
621 process clients, this dict MUST contain strictly pickle-safe values.
622 """
623
624 return dict(number=self.current_cell_number)
625
626 def setup_namespace(self):
627 """ Add things to the namespace.
628 """
629
630 self.user_ns.setdefault('__name__', '__main__')
631 self.user_ns.setdefault('__builtins__', __builtin__)
632 self.user_ns['__IP'] = self
633 if self.raw_input_builtin is not None:
634 self.user_ns['raw_input'] = self.raw_input_builtin
635 if self.input_builtin is not None:
636 self.user_ns['input'] = self.input_builtin
637
638 builtin_additions = dict(
639 ipmagic=self.ipmagic,
640 )
641 __builtin__.__dict__.update(builtin_additions)
642
643 if self.history is not None:
644 self.history.setup_namespace(self.user_ns)
645
646 def set_traps(self):
647 """ Set all of the output, display, and traceback traps.
648 """
649
650 self.output_trap.set()
651 self.display_trap.set()
652 self.traceback_trap.set()
653
654 def unset_traps(self):
655 """ Unset all of the output, display, and traceback traps.
656 """
657
658 self.output_trap.unset()
659 self.display_trap.unset()
660 self.traceback_trap.unset()
661
662 def split_commands(self, python):
663 """ Split multiple lines of code into discrete commands that can be
664 executed singly.
665
666 Parameters
667 ----------
668 python : str
669 Pure, exec'able Python code.
670
671 Returns
672 -------
673 commands : list of str
674 Separate commands that can be exec'ed independently.
675 """
676
677 # compiler.parse treats trailing spaces after a newline as a
678 # SyntaxError. This is different than codeop.CommandCompiler, which
679 # will compile the trailng spaces just fine. We simply strip any
680 # trailing whitespace off. Passing a string with trailing whitespace
681 # to exec will fail however. There seems to be some inconsistency in
682 # how trailing whitespace is handled, but this seems to work.
683 python = python.strip()
684
685 # The compiler module will parse the code into an abstract syntax tree.
686 ast = compiler.parse(python)
687
688 # Uncomment to help debug the ast tree
689 # for n in ast.node:
690 # print n.lineno,'->',n
691
692 # Each separate command is available by iterating over ast.node. The
693 # lineno attribute is the line number (1-indexed) beginning the commands
694 # suite.
695 # lines ending with ";" yield a Discard Node that doesn't have a lineno
696 # attribute. These nodes can and should be discarded. But there are
697 # other situations that cause Discard nodes that shouldn't be discarded.
698 # We might eventually discover other cases where lineno is None and have
699 # to put in a more sophisticated test.
700 linenos = [x.lineno-1 for x in ast.node if x.lineno is not None]
701
702 # When we finally get the slices, we will need to slice all the way to
703 # the end even though we don't have a line number for it. Fortunately,
704 # None does the job nicely.
705 linenos.append(None)
706 lines = python.splitlines()
707
708 # Create a list of atomic commands.
709 cmds = []
710 for i, j in zip(linenos[:-1], linenos[1:]):
711 cmd = lines[i:j]
712 if cmd:
713 cmds.append('\n'.join(cmd)+'\n')
714
715 return cmds
716
717 def error(self, text):
718 """ Pass an error message back to the shell.
719
720 Preconditions
721 -------------
722 This should only be called when self.message is set. In other words,
723 when code is being executed.
724
725 Parameters
726 ----------
727 text : str
728 """
729
730 errors = self.message.get('IPYTHON_ERROR', [])
731 errors.append(text)
732
733 def var_expand(self, template):
734 """ Expand $variables in the current namespace using Itpl.
735
736 Parameters
737 ----------
738 template : str
739 """
740
741 return str(ItplNS(template, self.user_ns))
742
743 def _possible_macro(self, obj):
744 """ If the object is a macro, execute it.
745 """
746
747 if isinstance(obj, Macro):
748 self.execute_macro(obj)
749
@@ -0,0 +1,34 b''
1 # encoding: utf-8
2
3 """Support for interactive macros in IPython"""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 class Macro:
19 """Simple class to store the value of macros as strings.
20
21 This allows us to later exec them by checking when something is an
22 instance of this class."""
23
24 def __init__(self,data):
25
26 # store the macro value, as a single string which can be evaluated by
27 # runlines()
28 self.value = ''.join(data).rstrip()+'\n'
29
30 def __str__(self):
31 return self.value
32
33 def __repr__(self):
34 return 'IPython.macro.Macro(%s)' % repr(self.value) No newline at end of file
@@ -0,0 +1,147 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 import os
17 import __builtin__
18
19 # Local imports.
20 from util import Bunch
21
22
23 # fixme: RTK thinks magics should be implemented as separate classes rather than
24 # methods on a single class. This would give us the ability to plug new magics
25 # in and configure them separately.
26
27 class Magic(object):
28 """ An object that maintains magic functions.
29 """
30
31 def __init__(self, interpreter, config=None):
32 # A reference to the interpreter.
33 self.interpreter = interpreter
34
35 # A reference to the configuration object.
36 if config is None:
37 # fixme: we need a better place to store this information.
38 config = Bunch(ESC_MAGIC='%')
39 self.config = config
40
41 def has_magic(self, name):
42 """ Return True if this object provides a given magic.
43
44 Parameters
45 ----------
46 name : str
47 """
48
49 return hasattr(self, 'magic_' + name)
50
51 def object_find(self, name):
52 """ Find an object in the available namespaces.
53
54 fixme: this should probably be moved elsewhere. The interpreter?
55 """
56
57 name = name.strip()
58
59 # Namespaces to search.
60 # fixme: implement internal and alias namespaces.
61 user_ns = self.interpreter.user_ns
62 internal_ns = {}
63 builtin_ns = __builtin__.__dict__
64 alias_ns = {}
65
66 # Order the namespaces.
67 namespaces = [
68 ('Interactive', user_ns),
69 ('IPython internal', internal_ns),
70 ('Python builtin', builtin_ns),
71 ('Alias', alias_ns),
72 ]
73
74 # Initialize all results.
75 found = False
76 obj = None
77 space = None
78 ds = None
79 ismagic = False
80 isalias = False
81
82 # Look for the given name by splitting it in parts. If the head is
83 # found, then we look for all the remaining parts as members, and only
84 # declare success if we can find them all.
85 parts = name.split('.')
86 head, rest = parts[0], parts[1:]
87 for nsname, ns in namespaces:
88 try:
89 obj = ns[head]
90 except KeyError:
91 continue
92 else:
93 for part in rest:
94 try:
95 obj = getattr(obj, part)
96 except:
97 # Blanket except b/c some badly implemented objects
98 # allow __getattr__ to raise exceptions other than
99 # AttributeError, which then crashes us.
100 break
101 else:
102 # If we finish the for loop (no break), we got all members
103 found = True
104 space = nsname
105 isalias = (ns == alias_ns)
106 break # namespace loop
107
108 # Try to see if it is a magic.
109 if not found:
110 if name.startswith(self.config.ESC_MAGIC):
111 name = name[1:]
112 obj = getattr(self, 'magic_' + name, None)
113 if obj is not None:
114 found = True
115 space = 'IPython internal'
116 ismagic = True
117
118 # Last try: special-case some literals like '', [], {}, etc:
119 if not found and head in ["''", '""', '[]', '{}', '()']:
120 obj = eval(head)
121 found = True
122 space = 'Interactive'
123
124 return dict(
125 found=found,
126 obj=obj,
127 namespace=space,
128 ismagic=ismagic,
129 isalias=isalias,
130 )
131
132
133
134
135
136 def magic_pwd(self, parameter_s=''):
137 """ Return the current working directory path.
138 """
139 return os.getcwd()
140
141 def magic_env(self, parameter_s=''):
142 """ List environment variables.
143 """
144
145 return os.environ.data
146
147
@@ -0,0 +1,98 b''
1 # encoding: utf-8
2
3 """Storage for the responses from the interpreter."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18
19 class IMessageCache(object):
20 """ Storage for the response from the interpreter.
21 """
22
23 def add_message(self, i, message):
24 """ Add a message dictionary to the cache.
25
26 Parameters
27 ----------
28 i : int
29 message : dict
30 """
31
32 def get_message(self, i=None):
33 """ Get the message from the cache.
34
35 Parameters
36 ----------
37 i : int, optional
38 The number of the message. If not provided, return the
39 highest-numbered message.
40
41 Returns
42 -------
43 message : dict
44
45 Raises
46 ------
47 IndexError if the message does not exist in the cache.
48 """
49
50
51 class SimpleMessageCache(object):
52 """ Simple dictionary-based, in-memory storage of the responses from the
53 interpreter.
54 """
55
56 def __init__(self):
57 self.cache = {}
58
59 def add_message(self, i, message):
60 """ Add a message dictionary to the cache.
61
62 Parameters
63 ----------
64 i : int
65 message : dict
66 """
67
68 self.cache[i] = message
69
70 def get_message(self, i=None):
71 """ Get the message from the cache.
72
73 Parameters
74 ----------
75 i : int, optional
76 The number of the message. If not provided, return the
77 highest-numbered message.
78
79 Returns
80 -------
81 message : dict
82
83 Raises
84 ------
85 IndexError if the message does not exist in the cache.
86 """
87 if i is None:
88 keys = self.cache.keys()
89 if len(keys) == 0:
90 raise IndexError("index %r out of range" % i)
91 else:
92 i = max(self.cache.keys())
93 try:
94 return self.cache[i]
95 except KeyError:
96 # IndexError is more appropriate, here.
97 raise IndexError("index %r out of range" % i)
98
@@ -0,0 +1,99 b''
1 # encoding: utf-8
2
3 """ Trap stdout/stderr."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import sys
19 from cStringIO import StringIO
20
21
22 class OutputTrap(object):
23 """ Object which can trap text sent to stdout and stderr.
24 """
25
26 def __init__(self):
27 # Filelike objects to store stdout/stderr text.
28 self.out = StringIO()
29 self.err = StringIO()
30
31 # Boolean to check if the stdout/stderr hook is set.
32 self.out_set = False
33 self.err_set = False
34
35 @property
36 def out_text(self):
37 """ Return the text currently in the stdout buffer.
38 """
39 return self.out.getvalue()
40
41 @property
42 def err_text(self):
43 """ Return the text currently in the stderr buffer.
44 """
45 return self.err.getvalue()
46
47 def set(self):
48 """ Set the hooks.
49 """
50
51 if sys.stdout is not self.out:
52 self._out_save = sys.stdout
53 sys.stdout = self.out
54 self.out_set = True
55
56 if sys.stderr is not self.err:
57 self._err_save = sys.stderr
58 sys.stderr = self.err
59 self.err_set = True
60
61 def unset(self):
62 """ Remove the hooks.
63 """
64
65 sys.stdout = self._out_save
66 self.out_set = False
67
68 sys.stderr = self._err_save
69 self.err_set = False
70
71 def clear(self):
72 """ Clear out the buffers.
73 """
74
75 self.out.close()
76 self.out = StringIO()
77
78 self.err.close()
79 self.err = StringIO()
80
81 def add_to_message(self, message):
82 """ Add the text from stdout and stderr to the message from the
83 interpreter to its listeners.
84
85 Parameters
86 ----------
87 message : dict
88 """
89
90 out_text = self.out_text
91 if out_text:
92 message['stdout'] = out_text
93
94 err_text = self.err_text
95 if err_text:
96 message['stderr'] = err_text
97
98
99
This diff has been collapsed as it changes many lines, (591 lines changed) Show them Hide them
@@ -0,0 +1,591 b''
1 # encoding: utf-8
2
3 """Classes for handling input/output prompts."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython import Release
19 __author__ = '%s <%s>' % Release.authors['Fernando']
20 __license__ = Release.license
21 __version__ = Release.version
22
23 #****************************************************************************
24 # Required modules
25 import __builtin__
26 import os
27 import socket
28 import sys
29 import time
30
31 # IPython's own
32 from IPython.external.Itpl import ItplNS
33 from macro import Macro
34
35 # Temporarily use this until it is ported to ipython1
36
37 from IPython import ColorANSI
38 from IPython.ipstruct import Struct
39 from IPython.genutils import *
40 from IPython.ipapi import TryNext
41
42 #****************************************************************************
43 #Color schemes for Prompts.
44
45 PromptColors = ColorANSI.ColorSchemeTable()
46 InputColors = ColorANSI.InputTermColors # just a shorthand
47 Colors = ColorANSI.TermColors # just a shorthand
48
49
50 __PColNoColor = ColorANSI.ColorScheme(
51 'NoColor',
52 in_prompt = InputColors.NoColor, # Input prompt
53 in_number = InputColors.NoColor, # Input prompt number
54 in_prompt2 = InputColors.NoColor, # Continuation prompt
55 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
56
57 out_prompt = Colors.NoColor, # Output prompt
58 out_number = Colors.NoColor, # Output prompt number
59
60 normal = Colors.NoColor # color off (usu. Colors.Normal)
61 )
62
63 PromptColors.add_scheme(__PColNoColor)
64
65 # make some schemes as instances so we can copy them for modification easily:
66 __PColLinux = __PColNoColor.copy('Linux')
67 # Don't forget to enter it into the table!
68 PromptColors.add_scheme(__PColLinux)
69 __PColLightBG = __PColLinux.copy('LightBG')
70 PromptColors.add_scheme(__PColLightBG)
71
72 del Colors,InputColors
73
74 #-----------------------------------------------------------------------------
75 def multiple_replace(dict, text):
76 """ Replace in 'text' all occurences of any key in the given
77 dictionary by its corresponding value. Returns the new string."""
78
79 # Function by Xavier Defrang, originally found at:
80 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
81
82 # Create a regular expression from the dictionary keys
83 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
84 # For each match, look-up corresponding value in dictionary
85 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
86
87 #-----------------------------------------------------------------------------
88 # Special characters that can be used in prompt templates, mainly bash-like
89
90 # If $HOME isn't defined (Windows), make it an absurd string so that it can
91 # never be expanded out into '~'. Basically anything which can never be a
92 # reasonable directory name will do, we just want the $HOME -> '~' operation
93 # to become a no-op. We pre-compute $HOME here so it's not done on every
94 # prompt call.
95
96 # FIXME:
97
98 # - This should be turned into a class which does proper namespace management,
99 # since the prompt specials need to be evaluated in a certain namespace.
100 # Currently it's just globals, which need to be managed manually by code
101 # below.
102
103 # - I also need to split up the color schemes from the prompt specials
104 # somehow. I don't have a clean design for that quite yet.
105
106 HOME = os.environ.get("HOME","//////:::::ZZZZZ,,,~~~")
107
108 # We precompute a few more strings here for the prompt_specials, which are
109 # fixed once ipython starts. This reduces the runtime overhead of computing
110 # prompt strings.
111 USER = os.environ.get("USER")
112 HOSTNAME = socket.gethostname()
113 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
114 ROOT_SYMBOL = "$#"[os.name=='nt' or os.getuid()==0]
115
116 prompt_specials_color = {
117 # Prompt/history count
118 '%n' : '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
119 r'\#': '${self.col_num}' '${self.cache.prompt_count}' '${self.col_p}',
120 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
121 # can get numbers displayed in whatever color they want.
122 r'\N': '${self.cache.prompt_count}',
123 # Prompt/history count, with the actual digits replaced by dots. Used
124 # mainly in continuation prompts (prompt_in2)
125 r'\D': '${"."*len(str(self.cache.prompt_count))}',
126 # Current working directory
127 r'\w': '${os.getcwd()}',
128 # Current time
129 r'\t' : '${time.strftime("%H:%M:%S")}',
130 # Basename of current working directory.
131 # (use os.sep to make this portable across OSes)
132 r'\W' : '${os.getcwd().split("%s")[-1]}' % os.sep,
133 # These X<N> are an extension to the normal bash prompts. They return
134 # N terms of the path, after replacing $HOME with '~'
135 r'\X0': '${os.getcwd().replace("%s","~")}' % HOME,
136 r'\X1': '${self.cwd_filt(1)}',
137 r'\X2': '${self.cwd_filt(2)}',
138 r'\X3': '${self.cwd_filt(3)}',
139 r'\X4': '${self.cwd_filt(4)}',
140 r'\X5': '${self.cwd_filt(5)}',
141 # Y<N> are similar to X<N>, but they show '~' if it's the directory
142 # N+1 in the list. Somewhat like %cN in tcsh.
143 r'\Y0': '${self.cwd_filt2(0)}',
144 r'\Y1': '${self.cwd_filt2(1)}',
145 r'\Y2': '${self.cwd_filt2(2)}',
146 r'\Y3': '${self.cwd_filt2(3)}',
147 r'\Y4': '${self.cwd_filt2(4)}',
148 r'\Y5': '${self.cwd_filt2(5)}',
149 # Hostname up to first .
150 r'\h': HOSTNAME_SHORT,
151 # Full hostname
152 r'\H': HOSTNAME,
153 # Username of current user
154 r'\u': USER,
155 # Escaped '\'
156 '\\\\': '\\',
157 # Newline
158 r'\n': '\n',
159 # Carriage return
160 r'\r': '\r',
161 # Release version
162 r'\v': __version__,
163 # Root symbol ($ or #)
164 r'\$': ROOT_SYMBOL,
165 }
166
167 # A copy of the prompt_specials dictionary but with all color escapes removed,
168 # so we can correctly compute the prompt length for the auto_rewrite method.
169 prompt_specials_nocolor = prompt_specials_color.copy()
170 prompt_specials_nocolor['%n'] = '${self.cache.prompt_count}'
171 prompt_specials_nocolor[r'\#'] = '${self.cache.prompt_count}'
172
173 # Add in all the InputTermColors color escapes as valid prompt characters.
174 # They all get added as \\C_COLORNAME, so that we don't have any conflicts
175 # with a color name which may begin with a letter used by any other of the
176 # allowed specials. This of course means that \\C will never be allowed for
177 # anything else.
178 input_colors = ColorANSI.InputTermColors
179 for _color in dir(input_colors):
180 if _color[0] != '_':
181 c_name = r'\C_'+_color
182 prompt_specials_color[c_name] = getattr(input_colors,_color)
183 prompt_specials_nocolor[c_name] = ''
184
185 # we default to no color for safety. Note that prompt_specials is a global
186 # variable used by all prompt objects.
187 prompt_specials = prompt_specials_nocolor
188
189 #-----------------------------------------------------------------------------
190 def str_safe(arg):
191 """Convert to a string, without ever raising an exception.
192
193 If str(arg) fails, <ERROR: ... > is returned, where ... is the exception
194 error message."""
195
196 try:
197 out = str(arg)
198 except UnicodeError:
199 try:
200 out = arg.encode('utf_8','replace')
201 except Exception,msg:
202 # let's keep this little duplication here, so that the most common
203 # case doesn't suffer from a double try wrapping.
204 out = '<ERROR: %s>' % msg
205 except Exception,msg:
206 out = '<ERROR: %s>' % msg
207 return out
208
209 class BasePrompt(object):
210 """Interactive prompt similar to Mathematica's."""
211
212 def _get_p_template(self):
213 return self._p_template
214
215 def _set_p_template(self,val):
216 self._p_template = val
217 self.set_p_str()
218
219 p_template = property(_get_p_template,_set_p_template,
220 doc='Template for prompt string creation')
221
222 def __init__(self,cache,sep,prompt,pad_left=False):
223
224 # Hack: we access information about the primary prompt through the
225 # cache argument. We need this, because we want the secondary prompt
226 # to be aligned with the primary one. Color table info is also shared
227 # by all prompt classes through the cache. Nice OO spaghetti code!
228 self.cache = cache
229 self.sep = sep
230
231 # regexp to count the number of spaces at the end of a prompt
232 # expression, useful for prompt auto-rewriting
233 self.rspace = re.compile(r'(\s*)$')
234 # Flag to left-pad prompt strings to match the length of the primary
235 # prompt
236 self.pad_left = pad_left
237
238 # Set template to create each actual prompt (where numbers change).
239 # Use a property
240 self.p_template = prompt
241 self.set_p_str()
242
243 def set_p_str(self):
244 """ Set the interpolating prompt strings.
245
246 This must be called every time the color settings change, because the
247 prompt_specials global may have changed."""
248
249 import os,time # needed in locals for prompt string handling
250 loc = locals()
251 self.p_str = ItplNS('%s%s%s' %
252 ('${self.sep}${self.col_p}',
253 multiple_replace(prompt_specials, self.p_template),
254 '${self.col_norm}'),self.cache.user_ns,loc)
255
256 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
257 self.p_template),
258 self.cache.user_ns,loc)
259
260 def write(self,msg): # dbg
261 sys.stdout.write(msg)
262 return ''
263
264 def __str__(self):
265 """Return a string form of the prompt.
266
267 This for is useful for continuation and output prompts, since it is
268 left-padded to match lengths with the primary one (if the
269 self.pad_left attribute is set)."""
270
271 out_str = str_safe(self.p_str)
272 if self.pad_left:
273 # We must find the amount of padding required to match lengths,
274 # taking the color escapes (which are invisible on-screen) into
275 # account.
276 esc_pad = len(out_str) - len(str_safe(self.p_str_nocolor))
277 format = '%%%ss' % (len(str(self.cache.last_prompt))+esc_pad)
278 return format % out_str
279 else:
280 return out_str
281
282 # these path filters are put in as methods so that we can control the
283 # namespace where the prompt strings get evaluated
284 def cwd_filt(self,depth):
285 """Return the last depth elements of the current working directory.
286
287 $HOME is always replaced with '~'.
288 If depth==0, the full path is returned."""
289
290 cwd = os.getcwd().replace(HOME,"~")
291 out = os.sep.join(cwd.split(os.sep)[-depth:])
292 if out:
293 return out
294 else:
295 return os.sep
296
297 def cwd_filt2(self,depth):
298 """Return the last depth elements of the current working directory.
299
300 $HOME is always replaced with '~'.
301 If depth==0, the full path is returned."""
302
303 cwd = os.getcwd().replace(HOME,"~").split(os.sep)
304 if '~' in cwd and len(cwd) == depth+1:
305 depth += 1
306 out = os.sep.join(cwd[-depth:])
307 if out:
308 return out
309 else:
310 return os.sep
311
312 class Prompt1(BasePrompt):
313 """Input interactive prompt similar to Mathematica's."""
314
315 def __init__(self,cache,sep='\n',prompt='In [\\#]: ',pad_left=True):
316 BasePrompt.__init__(self,cache,sep,prompt,pad_left)
317
318 def set_colors(self):
319 self.set_p_str()
320 Colors = self.cache.color_table.active_colors # shorthand
321 self.col_p = Colors.in_prompt
322 self.col_num = Colors.in_number
323 self.col_norm = Colors.in_normal
324 # We need a non-input version of these escapes for the '--->'
325 # auto-call prompts used in the auto_rewrite() method.
326 self.col_p_ni = self.col_p.replace('\001','').replace('\002','')
327 self.col_norm_ni = Colors.normal
328
329 def __str__(self):
330 self.cache.prompt_count += 1
331 self.cache.last_prompt = str_safe(self.p_str_nocolor).split('\n')[-1]
332 return str_safe(self.p_str)
333
334 def auto_rewrite(self):
335 """Print a string of the form '--->' which lines up with the previous
336 input string. Useful for systems which re-write the user input when
337 handling automatically special syntaxes."""
338
339 curr = str(self.cache.last_prompt)
340 nrspaces = len(self.rspace.search(curr).group())
341 return '%s%s>%s%s' % (self.col_p_ni,'-'*(len(curr)-nrspaces-1),
342 ' '*nrspaces,self.col_norm_ni)
343
344 class PromptOut(BasePrompt):
345 """Output interactive prompt similar to Mathematica's."""
346
347 def __init__(self,cache,sep='',prompt='Out[\\#]: ',pad_left=True):
348 BasePrompt.__init__(self,cache,sep,prompt,pad_left)
349 if not self.p_template:
350 self.__str__ = lambda: ''
351
352 def set_colors(self):
353 self.set_p_str()
354 Colors = self.cache.color_table.active_colors # shorthand
355 self.col_p = Colors.out_prompt
356 self.col_num = Colors.out_number
357 self.col_norm = Colors.normal
358
359 class Prompt2(BasePrompt):
360 """Interactive continuation prompt."""
361
362 def __init__(self,cache,prompt=' .\\D.: ',pad_left=True):
363 self.cache = cache
364 self.p_template = prompt
365 self.pad_left = pad_left
366 self.set_p_str()
367
368 def set_p_str(self):
369 import os,time # needed in locals for prompt string handling
370 loc = locals()
371 self.p_str = ItplNS('%s%s%s' %
372 ('${self.col_p2}',
373 multiple_replace(prompt_specials, self.p_template),
374 '$self.col_norm'),
375 self.cache.user_ns,loc)
376 self.p_str_nocolor = ItplNS(multiple_replace(prompt_specials_nocolor,
377 self.p_template),
378 self.cache.user_ns,loc)
379
380 def set_colors(self):
381 self.set_p_str()
382 Colors = self.cache.color_table.active_colors
383 self.col_p2 = Colors.in_prompt2
384 self.col_norm = Colors.in_normal
385 # FIXME (2004-06-16) HACK: prevent crashes for users who haven't
386 # updated their prompt_in2 definitions. Remove eventually.
387 self.col_p = Colors.out_prompt
388 self.col_num = Colors.out_number
389
390
391 #-----------------------------------------------------------------------------
392 class CachedOutput:
393 """Class for printing output from calculations while keeping a cache of
394 reults. It dynamically creates global variables prefixed with _ which
395 contain these results.
396
397 Meant to be used as a sys.displayhook replacement, providing numbered
398 prompts and cache services.
399
400 Initialize with initial and final values for cache counter (this defines
401 the maximum size of the cache."""
402
403 def __init__(self,shell,cache_size,Pprint,
404 colors='NoColor',input_sep='\n',
405 output_sep='\n',output_sep2='',
406 ps1 = None, ps2 = None,ps_out = None,pad_left=True):
407
408 cache_size_min = 3
409 if cache_size <= 0:
410 self.do_full_cache = 0
411 cache_size = 0
412 elif cache_size < cache_size_min:
413 self.do_full_cache = 0
414 cache_size = 0
415 warn('caching was disabled (min value for cache size is %s).' %
416 cache_size_min,level=3)
417 else:
418 self.do_full_cache = 1
419
420 self.cache_size = cache_size
421 self.input_sep = input_sep
422
423 # we need a reference to the user-level namespace
424 self.shell = shell
425 self.user_ns = shell.user_ns
426 # and to the user's input
427 self.input_hist = shell.history.input_cache
428
429 # Set input prompt strings and colors
430 if cache_size == 0:
431 if ps1.find('%n') > -1 or ps1.find(r'\#') > -1 \
432 or ps1.find(r'\N') > -1:
433 ps1 = '>>> '
434 if ps2.find('%n') > -1 or ps2.find(r'\#') > -1 \
435 or ps2.find(r'\N') > -1:
436 ps2 = '... '
437 self.ps1_str = self._set_prompt_str(ps1,'In [\\#]: ','>>> ')
438 self.ps2_str = self._set_prompt_str(ps2,' .\\D.: ','... ')
439 self.ps_out_str = self._set_prompt_str(ps_out,'Out[\\#]: ','')
440
441 self.color_table = PromptColors
442 self.prompt1 = Prompt1(self,sep=input_sep,prompt=self.ps1_str,
443 pad_left=pad_left)
444 self.prompt2 = Prompt2(self,prompt=self.ps2_str,pad_left=pad_left)
445 self.prompt_out = PromptOut(self,sep='',prompt=self.ps_out_str,
446 pad_left=pad_left)
447 self.set_colors(colors)
448
449 # other more normal stuff
450 # b/c each call to the In[] prompt raises it by 1, even the first.
451 self.prompt_count = 0
452 # Store the last prompt string each time, we need it for aligning
453 # continuation and auto-rewrite prompts
454 self.last_prompt = ''
455 self.Pprint = Pprint
456 self.output_sep = output_sep
457 self.output_sep2 = output_sep2
458 self._,self.__,self.___ = '','',''
459 self.pprint_types = map(type,[(),[],{}])
460
461 # these are deliberately global:
462 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
463 self.user_ns.update(to_user_ns)
464
465 def _set_prompt_str(self,p_str,cache_def,no_cache_def):
466 if p_str is None:
467 if self.do_full_cache:
468 return cache_def
469 else:
470 return no_cache_def
471 else:
472 return p_str
473
474 def set_colors(self,colors):
475 """Set the active color scheme and configure colors for the three
476 prompt subsystems."""
477
478 # FIXME: the prompt_specials global should be gobbled inside this
479 # class instead. Do it when cleaning up the whole 3-prompt system.
480 global prompt_specials
481 if colors.lower()=='nocolor':
482 prompt_specials = prompt_specials_nocolor
483 else:
484 prompt_specials = prompt_specials_color
485
486 self.color_table.set_active_scheme(colors)
487 self.prompt1.set_colors()
488 self.prompt2.set_colors()
489 self.prompt_out.set_colors()
490
491 def __call__(self,arg=None):
492 """Printing with history cache management.
493
494 This is invoked everytime the interpreter needs to print, and is
495 activated by setting the variable sys.displayhook to it."""
496
497 # If something injected a '_' variable in __builtin__, delete
498 # ipython's automatic one so we don't clobber that. gettext() in
499 # particular uses _, so we need to stay away from it.
500 if '_' in __builtin__.__dict__:
501 try:
502 del self.user_ns['_']
503 except KeyError:
504 pass
505 if arg is not None:
506 cout_write = Term.cout.write # fast lookup
507 # first handle the cache and counters
508
509 # do not print output if input ends in ';'
510 if self.input_hist[self.prompt_count].endswith(';\n'):
511 return
512 # don't use print, puts an extra space
513 cout_write(self.output_sep)
514 outprompt = self.shell.hooks.generate_output_prompt()
515 if self.do_full_cache:
516 cout_write(outprompt)
517
518 # and now call a possibly user-defined print mechanism
519 manipulated_val = self.display(arg)
520
521 # user display hooks can change the variable to be stored in
522 # output history
523
524 if manipulated_val is not None:
525 arg = manipulated_val
526
527 # avoid recursive reference when displaying _oh/Out
528 if arg is not self.user_ns['_oh']:
529 self.update(arg)
530
531 cout_write(self.output_sep2)
532 Term.cout.flush()
533
534 def _display(self,arg):
535 """Default printer method, uses pprint.
536
537 Do ip.set_hook("result_display", my_displayhook) for custom result
538 display, e.g. when your own objects need special formatting.
539 """
540 try:
541 return IPython.generics.result_display(arg)
542 except TryNext:
543 return self.shell.hooks.result_display(arg)
544
545 # Assign the default display method:
546 display = _display
547
548 def update(self,arg):
549 #print '***cache_count', self.cache_count # dbg
550 if len(self.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
551 warn('Output cache limit (currently '+
552 `self.cache_size`+' entries) hit.\n'
553 'Flushing cache and resetting history counter...\n'
554 'The only history variables available will be _,__,___ and _1\n'
555 'with the current result.')
556
557 self.flush()
558 # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise
559 # we cause buggy behavior for things like gettext).
560 if '_' not in __builtin__.__dict__:
561 self.___ = self.__
562 self.__ = self._
563 self._ = arg
564 self.user_ns.update({'_':self._,'__':self.__,'___':self.___})
565
566 # hackish access to top-level namespace to create _1,_2... dynamically
567 to_main = {}
568 if self.do_full_cache:
569 new_result = '_'+`self.prompt_count`
570 to_main[new_result] = arg
571 self.user_ns.update(to_main)
572 self.user_ns['_oh'][self.prompt_count] = arg
573
574 def flush(self):
575 if not self.do_full_cache:
576 raise ValueError,"You shouldn't have reached the cache flush "\
577 "if full caching is not enabled!"
578 # delete auto-generated vars from global namespace
579
580 for n in range(1,self.prompt_count + 1):
581 key = '_'+`n`
582 try:
583 del self.user_ns[key]
584 except: pass
585 self.user_ns['_oh'].clear()
586
587 if '_' not in __builtin__.__dict__:
588 self.user_ns.update({'_':None,'__':None, '___':None})
589 import gc
590 gc.collect() # xxx needed?
591
@@ -0,0 +1,357 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.test.test_shell -*-
3
4 """The core IPython Shell"""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import pprint
20 import signal
21 import sys
22 import threading
23 import time
24
25 from code import InteractiveConsole, softspace
26 from StringIO import StringIO
27
28 from IPython.OutputTrap import OutputTrap
29 from IPython import ultraTB
30
31 from IPython.kernel.error import NotDefined
32
33
34 class InteractiveShell(InteractiveConsole):
35 """The Basic IPython Shell class.
36
37 This class provides the basic capabilities of IPython. Currently
38 this class does not do anything IPython specific. That is, it is
39 just a python shell.
40
41 It is modelled on code.InteractiveConsole, but adds additional
42 capabilities. These additional capabilities are what give IPython
43 its power.
44
45 The current version of this class is meant to be a prototype that guides
46 the future design of the IPython core. This class must not use Twisted
47 in any way, but it must be designed in a way that makes it easy to
48 incorporate into Twisted and hook network protocols up to.
49
50 Some of the methods of this class comprise the official IPython core
51 interface. These methods must be tread safe and they must return types
52 that can be easily serialized by protocols such as PB, XML-RPC and SOAP.
53 Locks have been provided for making the methods thread safe, but additional
54 locks can be added as needed.
55
56 Any method that is meant to be a part of the official interface must also
57 be declared in the kernel.coreservice.ICoreService interface. Eventually
58 all other methods should have single leading underscores to note that they
59 are not designed to be 'public.' Currently, because this class inherits
60 from code.InteractiveConsole there are many private methods w/o leading
61 underscores. The interface should be as simple as possible and methods
62 should not be added to the interface unless they really need to be there.
63
64 Note:
65
66 - For now I am using methods named put/get to move objects in/out of the
67 users namespace. Originally, I was calling these methods push/pull, but
68 because code.InteractiveConsole already has a push method, I had to use
69 something different. Eventually, we probably won't subclass this class
70 so we can call these methods whatever we want. So, what do we want to
71 call them?
72 - We need a way of running the trapping of stdout/stderr in different ways.
73 We should be able to i) trap, ii) not trap at all or iii) trap and echo
74 things to stdout and stderr.
75 - How should errors be handled? Should exceptions be raised?
76 - What should methods that don't compute anything return? The default of
77 None?
78 """
79
80 def __init__(self, locals=None, filename="<console>"):
81 """Creates a new TrappingInteractiveConsole object."""
82 InteractiveConsole.__init__(self,locals,filename)
83 self._trap = OutputTrap(debug=0)
84 self._stdin = []
85 self._stdout = []
86 self._stderr = []
87 self._last_type = self._last_traceback = self._last_value = None
88 #self._namespace_lock = threading.Lock()
89 #self._command_lock = threading.Lock()
90 self.lastCommandIndex = -1
91 # I am using this user defined signal to interrupt the currently
92 # running command. I am not sure if this is the best way, but
93 # it is working!
94 # This doesn't work on Windows as it doesn't have this signal.
95 #signal.signal(signal.SIGUSR1, self._handleSIGUSR1)
96
97 # An exception handler. Experimental: later we need to make the
98 # modes/colors available to user configuration, etc.
99 self.tbHandler = ultraTB.FormattedTB(color_scheme='NoColor',
100 mode='Context',
101 tb_offset=2)
102
103 def _handleSIGUSR1(self, signum, frame):
104 """Handle the SIGUSR1 signal by printing to stderr."""
105 print>>sys.stderr, "Command stopped."
106
107 def _prefilter(self, line, more):
108 return line
109
110 def _trapRunlines(self, lines):
111 """
112 This executes the python source code, source, in the
113 self.locals namespace and traps stdout and stderr. Upon
114 exiting, self.out and self.err contain the values of
115 stdout and stderr for the last executed command only.
116 """
117
118 # Execute the code
119 #self._namespace_lock.acquire()
120 self._trap.flush()
121 self._trap.trap()
122 self._runlines(lines)
123 self.lastCommandIndex += 1
124 self._trap.release()
125 #self._namespace_lock.release()
126
127 # Save stdin, stdout and stderr to lists
128 #self._command_lock.acquire()
129 self._stdin.append(lines)
130 self._stdout.append(self.prune_output(self._trap.out.getvalue()))
131 self._stderr.append(self.prune_output(self._trap.err.getvalue()))
132 #self._command_lock.release()
133
134 def prune_output(self, s):
135 """Only return the first and last 1600 chars of stdout and stderr.
136
137 Something like this is required to make sure that the engine and
138 controller don't become overwhelmed by the size of stdout/stderr.
139 """
140 if len(s) > 3200:
141 return s[:1600] + '\n............\n' + s[-1600:]
142 else:
143 return s
144
145 # Lifted from iplib.InteractiveShell
146 def _runlines(self,lines):
147 """Run a string of one or more lines of source.
148
149 This method is capable of running a string containing multiple source
150 lines, as if they had been entered at the IPython prompt. Since it
151 exposes IPython's processing machinery, the given strings can contain
152 magic calls (%magic), special shell access (!cmd), etc."""
153
154 # We must start with a clean buffer, in case this is run from an
155 # interactive IPython session (via a magic, for example).
156 self.resetbuffer()
157 lines = lines.split('\n')
158 more = 0
159 for line in lines:
160 # skip blank lines so we don't mess up the prompt counter, but do
161 # NOT skip even a blank line if we are in a code block (more is
162 # true)
163 if line or more:
164 more = self.push((self._prefilter(line,more)))
165 # IPython's runsource returns None if there was an error
166 # compiling the code. This allows us to stop processing right
167 # away, so the user gets the error message at the right place.
168 if more is None:
169 break
170 # final newline in case the input didn't have it, so that the code
171 # actually does get executed
172 if more:
173 self.push('\n')
174
175 def runcode(self, code):
176 """Execute a code object.
177
178 When an exception occurs, self.showtraceback() is called to
179 display a traceback. All exceptions are caught except
180 SystemExit, which is reraised.
181
182 A note about KeyboardInterrupt: this exception may occur
183 elsewhere in this code, and may not always be caught. The
184 caller should be prepared to deal with it.
185
186 """
187
188 self._last_type = self._last_traceback = self._last_value = None
189 try:
190 exec code in self.locals
191 except:
192 # Since the exception info may need to travel across the wire, we
193 # pack it in right away. Note that we are abusing the exception
194 # value to store a fully formatted traceback, since the stack can
195 # not be serialized for network transmission.
196 et,ev,tb = sys.exc_info()
197 self._last_type = et
198 self._last_traceback = tb
199 tbinfo = self.tbHandler.text(et,ev,tb)
200 # Construct a meaningful traceback message for shipping over the
201 # wire.
202 buf = pprint.pformat(self.buffer)
203 try:
204 ename = et.__name__
205 except:
206 ename = et
207 msg = """\
208 %(ev)s
209 ***************************************************************************
210 An exception occurred in an IPython engine while executing user code.
211
212 Current execution buffer (lines being run):
213 %(buf)s
214
215 A full traceback from the actual engine:
216 %(tbinfo)s
217 ***************************************************************************
218 """ % locals()
219 self._last_value = msg
220 else:
221 if softspace(sys.stdout, 0):
222 print
223
224 ##################################################################
225 # Methods that are a part of the official interface
226 #
227 # These methods should also be put in the
228 # kernel.coreservice.ICoreService interface.
229 #
230 # These methods must conform to certain restrictions that allow
231 # them to be exposed to various network protocols:
232 #
233 # - As much as possible, these methods must return types that can be
234 # serialized by PB, XML-RPC and SOAP. None is OK.
235 # - Every method must be thread safe. There are some locks provided
236 # for this purpose, but new, specialized locks can be added to the
237 # class.
238 ##################################################################
239
240 # Methods for running code
241
242 def exc_info(self):
243 """Return exception information much like sys.exc_info().
244
245 This method returns the same (etype,evalue,tb) tuple as sys.exc_info,
246 but from the last time that the engine had an exception fire."""
247
248 return self._last_type,self._last_value,self._last_traceback
249
250 def execute(self, lines):
251 self._trapRunlines(lines)
252 if self._last_type is None:
253 return self.getCommand()
254 else:
255 raise self._last_type(self._last_value)
256
257 # Methods for working with the namespace
258
259 def put(self, key, value):
260 """Put value into locals namespace with name key.
261
262 I have often called this push(), but in this case the
263 InteractiveConsole class already defines a push() method that
264 is different.
265 """
266
267 if not isinstance(key, str):
268 raise TypeError, "Objects must be keyed by strings."
269 self.update({key:value})
270
271 def get(self, key):
272 """Gets an item out of the self.locals dict by key.
273
274 Raise NameError if the object doesn't exist.
275
276 I have often called this pull(). I still like that better.
277 """
278
279 class NotDefined(object):
280 """A class to signify an objects that is not in the users ns."""
281 pass
282
283 if not isinstance(key, str):
284 raise TypeError, "Objects must be keyed by strings."
285 result = self.locals.get(key, NotDefined())
286 if isinstance(result, NotDefined):
287 raise NameError('name %s is not defined' % key)
288 else:
289 return result
290
291
292 def update(self, dictOfData):
293 """Loads a dict of key value pairs into the self.locals namespace."""
294 if not isinstance(dictOfData, dict):
295 raise TypeError, "update() takes a dict object."
296 #self._namespace_lock.acquire()
297 self.locals.update(dictOfData)
298 #self._namespace_lock.release()
299
300 # Methods for getting stdout/stderr/stdin
301
302 def reset(self):
303 """Reset the InteractiveShell."""
304
305 #self._command_lock.acquire()
306 self._stdin = []
307 self._stdout = []
308 self._stderr = []
309 self.lastCommandIndex = -1
310 #self._command_lock.release()
311
312 #self._namespace_lock.acquire()
313 # preserve id, mpi objects
314 mpi = self.locals.get('mpi', None)
315 id = self.locals.get('id', None)
316 del self.locals
317 self.locals = {'mpi': mpi, 'id': id}
318 #self._namespace_lock.release()
319
320 def getCommand(self,i=None):
321 """Get the stdin/stdout/stderr of command i."""
322
323 #self._command_lock.acquire()
324
325
326 if i is not None and not isinstance(i, int):
327 raise TypeError("Command index not an int: " + str(i))
328
329 if i in range(self.lastCommandIndex + 1):
330 inResult = self._stdin[i]
331 outResult = self._stdout[i]
332 errResult = self._stderr[i]
333 cmdNum = i
334 elif i is None and self.lastCommandIndex >= 0:
335 inResult = self._stdin[self.lastCommandIndex]
336 outResult = self._stdout[self.lastCommandIndex]
337 errResult = self._stderr[self.lastCommandIndex]
338 cmdNum = self.lastCommandIndex
339 else:
340 inResult = None
341 outResult = None
342 errResult = None
343
344 #self._command_lock.release()
345
346 if inResult is not None:
347 return dict(commandIndex=cmdNum, stdin=inResult, stdout=outResult, stderr=errResult)
348 else:
349 raise IndexError("Command with index %s does not exist" % str(i))
350
351 def getLastCommandIndex(self):
352 """Get the index of the last command."""
353 #self._command_lock.acquire()
354 ind = self.lastCommandIndex
355 #self._command_lock.release()
356 return ind
357
@@ -0,0 +1,10 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #------------------------------------------------------------------------------- No newline at end of file
@@ -0,0 +1,67 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the shell.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import unittest
19 from IPython.kernel.core import shell
20
21 resultKeys = ('commandIndex', 'stdin', 'stdout', 'stderr')
22
23 class BasicShellTest(unittest.TestCase):
24
25 def setUp(self):
26 self.s = shell.InteractiveShell()
27
28 def testExecute(self):
29 commands = [(0,"a = 5","",""),
30 (1,"b = 10","",""),
31 (2,"c = a + b","",""),
32 (3,"print c","15\n",""),
33 (4,"import math","",""),
34 (5,"2.0*math.pi","6.2831853071795862\n","")]
35 for c in commands:
36 result = self.s.execute(c[1])
37 self.assertEquals(result, dict(zip(resultKeys,c)))
38
39 def testPutGet(self):
40 objs = [10,"hi there",1.2342354,{"p":(1,2)}]
41 for o in objs:
42 self.s.put("key",o)
43 value = self.s.get("key")
44 self.assertEquals(value,o)
45 self.assertRaises(TypeError, self.s.put,10)
46 self.assertRaises(TypeError, self.s.get,10)
47 self.s.reset()
48 self.assertRaises(NameError, self.s.get, 'a')
49
50 def testUpdate(self):
51 d = {"a": 10, "b": 34.3434, "c": "hi there"}
52 self.s.update(d)
53 for k in d.keys():
54 value = self.s.get(k)
55 self.assertEquals(value, d[k])
56 self.assertRaises(TypeError, self.s.update, [1,2,2])
57
58 def testCommand(self):
59 self.assertRaises(IndexError,self.s.getCommand)
60 self.s.execute("a = 5")
61 self.assertEquals(self.s.getCommand(), dict(zip(resultKeys, (0,"a = 5","",""))))
62 self.assertEquals(self.s.getCommand(0), dict(zip(resultKeys, (0,"a = 5","",""))))
63 self.s.reset()
64 self.assertEquals(self.s.getLastCommandIndex(),-1)
65 self.assertRaises(IndexError,self.s.getCommand)
66
67 No newline at end of file
@@ -0,0 +1,62 b''
1 # encoding: utf-8
2
3 """Some formatter objects to extract traceback information by replacing
4 sys.excepthook()."""
5
6 __docformat__ = "restructuredtext en"
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import traceback
20
21
22 class ITracebackFormatter(object):
23 """ Objects conforming to this interface will format tracebacks into other
24 objects.
25 """
26
27 # The kind of formatter.
28 kind = 'traceback'
29
30 # The unique identifier for this formatter.
31 identifier = None
32
33
34 def __call__(self, exc_type, exc_value, exc_traceback):
35 """ Return a formatted representation of a traceback.
36 """
37
38 raise NotImplementedError
39
40
41 class PlainTracebackFormatter(ITracebackFormatter):
42 """ Return a string with the regular traceback information.
43 """
44
45 # The unique identifier for this formatter.
46 identifier = 'plain'
47
48
49 def __init__(self, limit=None):
50 # The maximum number of stack levels to go back.
51 # None implies all stack levels are returned.
52 self.limit = limit
53
54 def __call__(self, exc_type, exc_value, exc_traceback):
55 """ Return a string with the regular traceback information.
56 """
57
58 lines = traceback.format_tb(exc_traceback, self.limit)
59 lines.append('%s: %s' % (exc_type.__name__, exc_value))
60 return '\n'.join(lines)
61
62
@@ -0,0 +1,83 b''
1 # encoding: utf-8
2
3 """Object to manage sys.excepthook()."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import sys
19
20
21 class TracebackTrap(object):
22 """ Object to trap and format tracebacks.
23 """
24
25 def __init__(self, formatters=None):
26 # A list of formatters to apply.
27 if formatters is None:
28 formatters = []
29 self.formatters = formatters
30
31 # All of the traceback information provided to sys.excepthook().
32 self.args = None
33
34 # The previous hook before we replace it.
35 self.old_hook = None
36
37
38 def hook(self, *args):
39 """ This method actually implements the hook.
40 """
41
42 self.args = args
43
44 def set(self):
45 """ Set the hook.
46 """
47
48 if sys.excepthook is not self.hook:
49 self.old_hook = sys.excepthook
50 sys.excepthook = self.hook
51
52 def unset(self):
53 """ Unset the hook.
54 """
55
56 sys.excepthook = self.old_hook
57
58 def clear(self):
59 """ Remove the stored traceback.
60 """
61
62 self.args = None
63
64 def add_to_message(self, message):
65 """ Add the formatted display of the traceback to the message dictionary
66 being returned from the interpreter to its listeners.
67
68 Parameters
69 ----------
70 message : dict
71 """
72
73 # If there was no traceback, then don't add anything.
74 if self.args is None:
75 return
76
77 # Go through the list of formatters and let them add their formatting.
78 traceback = {}
79 for formatter in self.formatters:
80 traceback[formatter.identifier] = formatter(*self.args)
81
82 message['traceback'] = traceback
83
This diff has been collapsed as it changes many lines, (1018 lines changed) Show them Hide them
@@ -0,0 +1,1018 b''
1 # encoding: utf-8
2
3 """
4 ultraTB.py -- Spice up your tracebacks!
5
6 * ColorTB
7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 ColorTB class is a solution to that problem. It colors the different parts of a
9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 text editor.
11
12 Installation instructions for ColorTB:
13 import sys,ultraTB
14 sys.excepthook = ultraTB.ColorTB()
15
16 * VerboseTB
17 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
18 of useful info when a traceback occurs. Ping originally had it spit out HTML
19 and intended it for CGI programmers, but why should they have all the fun? I
20 altered it to spit out colored text to the terminal. It's a bit overwhelming,
21 but kind of neat, and maybe useful for long-running programs that you believe
22 are bug-free. If a crash *does* occur in that type of program you want details.
23 Give it a shot--you'll love it or you'll hate it.
24
25 Note:
26
27 The Verbose mode prints the variables currently visible where the exception
28 happened (shortening their strings if too long). This can potentially be
29 very slow, if you happen to have a huge data structure whose string
30 representation is complex to compute. Your computer may appear to freeze for
31 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
32 with Ctrl-C (maybe hitting it more than once).
33
34 If you encounter this kind of situation often, you may want to use the
35 Verbose_novars mode instead of the regular Verbose, which avoids formatting
36 variables (but otherwise includes the information and context given by
37 Verbose).
38
39
40 Installation instructions for ColorTB:
41 import sys,ultraTB
42 sys.excepthook = ultraTB.VerboseTB()
43
44 Note: Much of the code in this module was lifted verbatim from the standard
45 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
46
47 * Color schemes
48 The colors are defined in the class TBTools through the use of the
49 ColorSchemeTable class. Currently the following exist:
50
51 - NoColor: allows all of this module to be used in any terminal (the color
52 escapes are just dummy blank strings).
53
54 - Linux: is meant to look good in a terminal like the Linux console (black
55 or very dark background).
56
57 - LightBG: similar to Linux but swaps dark/light colors to be more readable
58 in light background terminals.
59
60 You can implement other color schemes easily, the syntax is fairly
61 self-explanatory. Please send back new schemes you develop to the author for
62 possible inclusion in future releases.
63
64 $Id: ultraTB.py 2480 2007-07-06 19:33:43Z fperez $"""
65
66 __docformat__ = "restructuredtext en"
67
68 #-------------------------------------------------------------------------------
69 # Copyright (C) 2008 The IPython Development Team
70 #
71 # Distributed under the terms of the BSD License. The full license is in
72 # the file COPYING, distributed as part of this software.
73 #-------------------------------------------------------------------------------
74
75 #-------------------------------------------------------------------------------
76 # Imports
77 #-------------------------------------------------------------------------------
78
79 from IPython import Release
80 __author__ = '%s <%s>\n%s <%s>' % (Release.authors['Nathan']+
81 Release.authors['Fernando'])
82 __license__ = Release.license
83
84 # Required modules
85 import inspect
86 import keyword
87 import linecache
88 import os
89 import pydoc
90 import re
91 import string
92 import sys
93 import time
94 import tokenize
95 import traceback
96 import types
97
98 # For purposes of monkeypatching inspect to fix a bug in it.
99 from inspect import getsourcefile, getfile, getmodule,\
100 ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode
101
102
103 # IPython's own modules
104 # Modified pdb which doesn't damage IPython's readline handling
105 from IPython import Debugger, PyColorize
106 from IPython.ipstruct import Struct
107 from IPython.excolors import ExceptionColors
108 from IPython.genutils import Term,uniq_stable,error,info
109
110 # Globals
111 # amount of space to put line numbers before verbose tracebacks
112 INDENT_SIZE = 8
113
114 # Default color scheme. This is used, for example, by the traceback
115 # formatter. When running in an actual IPython instance, the user's rc.colors
116 # value is used, but havinga module global makes this functionality available
117 # to users of ultraTB who are NOT running inside ipython.
118 DEFAULT_SCHEME = 'NoColor'
119
120 #---------------------------------------------------------------------------
121 # Code begins
122
123 # Utility functions
124 def inspect_error():
125 """Print a message about internal inspect errors.
126
127 These are unfortunately quite common."""
128
129 error('Internal Python error in the inspect module.\n'
130 'Below is the traceback from this internal error.\n')
131
132
133 def findsource(object):
134 """Return the entire source file and starting line number for an object.
135
136 The argument may be a module, class, method, function, traceback, frame,
137 or code object. The source code is returned as a list of all the lines
138 in the file and the line number indexes a line in that list. An IOError
139 is raised if the source code cannot be retrieved.
140
141 FIXED version with which we monkeypatch the stdlib to work around a bug."""
142
143 file = getsourcefile(object) or getfile(object)
144 module = getmodule(object, file)
145 if module:
146 lines = linecache.getlines(file, module.__dict__)
147 else:
148 lines = linecache.getlines(file)
149 if not lines:
150 raise IOError('could not get source code')
151
152 if ismodule(object):
153 return lines, 0
154
155 if isclass(object):
156 name = object.__name__
157 pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
158 # make some effort to find the best matching class definition:
159 # use the one with the least indentation, which is the one
160 # that's most probably not inside a function definition.
161 candidates = []
162 for i in range(len(lines)):
163 match = pat.match(lines[i])
164 if match:
165 # if it's at toplevel, it's already the best one
166 if lines[i][0] == 'c':
167 return lines, i
168 # else add whitespace to candidate list
169 candidates.append((match.group(1), i))
170 if candidates:
171 # this will sort by whitespace, and by line number,
172 # less whitespace first
173 candidates.sort()
174 return lines, candidates[0][1]
175 else:
176 raise IOError('could not find class definition')
177
178 if ismethod(object):
179 object = object.im_func
180 if isfunction(object):
181 object = object.func_code
182 if istraceback(object):
183 object = object.tb_frame
184 if isframe(object):
185 object = object.f_code
186 if iscode(object):
187 if not hasattr(object, 'co_firstlineno'):
188 raise IOError('could not find function definition')
189 pat = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
190 pmatch = pat.match
191 # fperez - fix: sometimes, co_firstlineno can give a number larger than
192 # the length of lines, which causes an error. Safeguard against that.
193 lnum = min(object.co_firstlineno,len(lines))-1
194 while lnum > 0:
195 if pmatch(lines[lnum]): break
196 lnum -= 1
197
198 return lines, lnum
199 raise IOError('could not find code object')
200
201 # Monkeypatch inspect to apply our bugfix. This code only works with py25
202 if sys.version_info[:2] >= (2,5):
203 inspect.findsource = findsource
204
205 def _fixed_getinnerframes(etb, context=1,tb_offset=0):
206 import linecache
207 LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
208
209 records = inspect.getinnerframes(etb, context)
210
211 # If the error is at the console, don't build any context, since it would
212 # otherwise produce 5 blank lines printed out (there is no file at the
213 # console)
214 rec_check = records[tb_offset:]
215 try:
216 rname = rec_check[0][1]
217 if rname == '<ipython console>' or rname.endswith('<string>'):
218 return rec_check
219 except IndexError:
220 pass
221
222 aux = traceback.extract_tb(etb)
223 assert len(records) == len(aux)
224 for i, (file, lnum, _, _) in zip(range(len(records)), aux):
225 maybeStart = lnum-1 - context//2
226 start = max(maybeStart, 0)
227 end = start + context
228 lines = linecache.getlines(file)[start:end]
229 # pad with empty lines if necessary
230 if maybeStart < 0:
231 lines = (['\n'] * -maybeStart) + lines
232 if len(lines) < context:
233 lines += ['\n'] * (context - len(lines))
234 buf = list(records[i])
235 buf[LNUM_POS] = lnum
236 buf[INDEX_POS] = lnum - 1 - start
237 buf[LINES_POS] = lines
238 records[i] = tuple(buf)
239 return records[tb_offset:]
240
241 # Helper function -- largely belongs to VerboseTB, but we need the same
242 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
243 # can be recognized properly by ipython.el's py-traceback-line-re
244 # (SyntaxErrors have to be treated specially because they have no traceback)
245
246 _parser = PyColorize.Parser()
247
248 def _formatTracebackLines(lnum, index, lines, Colors, lvals=None,scheme=None):
249 numbers_width = INDENT_SIZE - 1
250 res = []
251 i = lnum - index
252
253 # This lets us get fully syntax-highlighted tracebacks.
254 if scheme is None:
255 try:
256 scheme = __IPYTHON__.rc.colors
257 except:
258 scheme = DEFAULT_SCHEME
259 _line_format = _parser.format2
260
261 for line in lines:
262 new_line, err = _line_format(line,'str',scheme)
263 if not err: line = new_line
264
265 if i == lnum:
266 # This is the line with the error
267 pad = numbers_width - len(str(i))
268 if pad >= 3:
269 marker = '-'*(pad-3) + '-> '
270 elif pad == 2:
271 marker = '> '
272 elif pad == 1:
273 marker = '>'
274 else:
275 marker = ''
276 num = marker + str(i)
277 line = '%s%s%s %s%s' %(Colors.linenoEm, num,
278 Colors.line, line, Colors.Normal)
279 else:
280 num = '%*s' % (numbers_width,i)
281 line = '%s%s%s %s' %(Colors.lineno, num,
282 Colors.Normal, line)
283
284 res.append(line)
285 if lvals and i == lnum:
286 res.append(lvals + '\n')
287 i = i + 1
288 return res
289
290
291 #---------------------------------------------------------------------------
292 # Module classes
293 class TBTools:
294 """Basic tools used by all traceback printer classes."""
295
296 def __init__(self,color_scheme = 'NoColor',call_pdb=False):
297 # Whether to call the interactive pdb debugger after printing
298 # tracebacks or not
299 self.call_pdb = call_pdb
300
301 # Create color table
302 self.color_scheme_table = ExceptionColors
303
304 self.set_colors(color_scheme)
305 self.old_scheme = color_scheme # save initial value for toggles
306
307 if call_pdb:
308 self.pdb = Debugger.Pdb(self.color_scheme_table.active_scheme_name)
309 else:
310 self.pdb = None
311
312 def set_colors(self,*args,**kw):
313 """Shorthand access to the color table scheme selector method."""
314
315 # Set own color table
316 self.color_scheme_table.set_active_scheme(*args,**kw)
317 # for convenience, set Colors to the active scheme
318 self.Colors = self.color_scheme_table.active_colors
319 # Also set colors of debugger
320 if hasattr(self,'pdb') and self.pdb is not None:
321 self.pdb.set_colors(*args,**kw)
322
323 def color_toggle(self):
324 """Toggle between the currently active color scheme and NoColor."""
325
326 if self.color_scheme_table.active_scheme_name == 'NoColor':
327 self.color_scheme_table.set_active_scheme(self.old_scheme)
328 self.Colors = self.color_scheme_table.active_colors
329 else:
330 self.old_scheme = self.color_scheme_table.active_scheme_name
331 self.color_scheme_table.set_active_scheme('NoColor')
332 self.Colors = self.color_scheme_table.active_colors
333
334 #---------------------------------------------------------------------------
335 class ListTB(TBTools):
336 """Print traceback information from a traceback list, with optional color.
337
338 Calling: requires 3 arguments:
339 (etype, evalue, elist)
340 as would be obtained by:
341 etype, evalue, tb = sys.exc_info()
342 if tb:
343 elist = traceback.extract_tb(tb)
344 else:
345 elist = None
346
347 It can thus be used by programs which need to process the traceback before
348 printing (such as console replacements based on the code module from the
349 standard library).
350
351 Because they are meant to be called without a full traceback (only a
352 list), instances of this class can't call the interactive pdb debugger."""
353
354 def __init__(self,color_scheme = 'NoColor'):
355 TBTools.__init__(self,color_scheme = color_scheme,call_pdb=0)
356
357 def __call__(self, etype, value, elist):
358 Term.cout.flush()
359 Term.cerr.flush()
360 print >> Term.cerr, self.text(etype,value,elist)
361
362 def text(self,etype, value, elist,context=5):
363 """Return a color formatted string with the traceback info."""
364
365 Colors = self.Colors
366 out_string = ['%s%s%s\n' % (Colors.topline,'-'*60,Colors.Normal)]
367 if elist:
368 out_string.append('Traceback %s(most recent call last)%s:' % \
369 (Colors.normalEm, Colors.Normal) + '\n')
370 out_string.extend(self._format_list(elist))
371 lines = self._format_exception_only(etype, value)
372 for line in lines[:-1]:
373 out_string.append(" "+line)
374 out_string.append(lines[-1])
375 return ''.join(out_string)
376
377 def _format_list(self, extracted_list):
378 """Format a list of traceback entry tuples for printing.
379
380 Given a list of tuples as returned by extract_tb() or
381 extract_stack(), return a list of strings ready for printing.
382 Each string in the resulting list corresponds to the item with the
383 same index in the argument list. Each string ends in a newline;
384 the strings may contain internal newlines as well, for those items
385 whose source text line is not None.
386
387 Lifted almost verbatim from traceback.py
388 """
389
390 Colors = self.Colors
391 list = []
392 for filename, lineno, name, line in extracted_list[:-1]:
393 item = ' File %s"%s"%s, line %s%d%s, in %s%s%s\n' % \
394 (Colors.filename, filename, Colors.Normal,
395 Colors.lineno, lineno, Colors.Normal,
396 Colors.name, name, Colors.Normal)
397 if line:
398 item = item + ' %s\n' % line.strip()
399 list.append(item)
400 # Emphasize the last entry
401 filename, lineno, name, line = extracted_list[-1]
402 item = '%s File %s"%s"%s, line %s%d%s, in %s%s%s%s\n' % \
403 (Colors.normalEm,
404 Colors.filenameEm, filename, Colors.normalEm,
405 Colors.linenoEm, lineno, Colors.normalEm,
406 Colors.nameEm, name, Colors.normalEm,
407 Colors.Normal)
408 if line:
409 item = item + '%s %s%s\n' % (Colors.line, line.strip(),
410 Colors.Normal)
411 list.append(item)
412 return list
413
414 def _format_exception_only(self, etype, value):
415 """Format the exception part of a traceback.
416
417 The arguments are the exception type and value such as given by
418 sys.exc_info()[:2]. The return value is a list of strings, each ending
419 in a newline. Normally, the list contains a single string; however,
420 for SyntaxError exceptions, it contains several lines that (when
421 printed) display detailed information about where the syntax error
422 occurred. The message indicating which exception occurred is the
423 always last string in the list.
424
425 Also lifted nearly verbatim from traceback.py
426 """
427
428 Colors = self.Colors
429 list = []
430 try:
431 stype = Colors.excName + etype.__name__ + Colors.Normal
432 except AttributeError:
433 stype = etype # String exceptions don't get special coloring
434 if value is None:
435 list.append( str(stype) + '\n')
436 else:
437 if etype is SyntaxError:
438 try:
439 msg, (filename, lineno, offset, line) = value
440 except:
441 pass
442 else:
443 #print 'filename is',filename # dbg
444 if not filename: filename = "<string>"
445 list.append('%s File %s"%s"%s, line %s%d%s\n' % \
446 (Colors.normalEm,
447 Colors.filenameEm, filename, Colors.normalEm,
448 Colors.linenoEm, lineno, Colors.Normal ))
449 if line is not None:
450 i = 0
451 while i < len(line) and line[i].isspace():
452 i = i+1
453 list.append('%s %s%s\n' % (Colors.line,
454 line.strip(),
455 Colors.Normal))
456 if offset is not None:
457 s = ' '
458 for c in line[i:offset-1]:
459 if c.isspace():
460 s = s + c
461 else:
462 s = s + ' '
463 list.append('%s%s^%s\n' % (Colors.caret, s,
464 Colors.Normal) )
465 value = msg
466 s = self._some_str(value)
467 if s:
468 list.append('%s%s:%s %s\n' % (str(stype), Colors.excName,
469 Colors.Normal, s))
470 else:
471 list.append('%s\n' % str(stype))
472 return list
473
474 def _some_str(self, value):
475 # Lifted from traceback.py
476 try:
477 return str(value)
478 except:
479 return '<unprintable %s object>' % type(value).__name__
480
481 #----------------------------------------------------------------------------
482 class VerboseTB(TBTools):
483 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
484 of HTML. Requires inspect and pydoc. Crazy, man.
485
486 Modified version which optionally strips the topmost entries from the
487 traceback, to be used with alternate interpreters (because their own code
488 would appear in the traceback)."""
489
490 def __init__(self,color_scheme = 'Linux',tb_offset=0,long_header=0,
491 call_pdb = 0, include_vars=1):
492 """Specify traceback offset, headers and color scheme.
493
494 Define how many frames to drop from the tracebacks. Calling it with
495 tb_offset=1 allows use of this handler in interpreters which will have
496 their own code at the top of the traceback (VerboseTB will first
497 remove that frame before printing the traceback info)."""
498 TBTools.__init__(self,color_scheme=color_scheme,call_pdb=call_pdb)
499 self.tb_offset = tb_offset
500 self.long_header = long_header
501 self.include_vars = include_vars
502
503 def text(self, etype, evalue, etb, context=5):
504 """Return a nice text document describing the traceback."""
505
506 # some locals
507 try:
508 etype = etype.__name__
509 except AttributeError:
510 pass
511 Colors = self.Colors # just a shorthand + quicker name lookup
512 ColorsNormal = Colors.Normal # used a lot
513 col_scheme = self.color_scheme_table.active_scheme_name
514 indent = ' '*INDENT_SIZE
515 em_normal = '%s\n%s%s' % (Colors.valEm, indent,ColorsNormal)
516 undefined = '%sundefined%s' % (Colors.em, ColorsNormal)
517 exc = '%s%s%s' % (Colors.excName,etype,ColorsNormal)
518
519 # some internal-use functions
520 def text_repr(value):
521 """Hopefully pretty robust repr equivalent."""
522 # this is pretty horrible but should always return *something*
523 try:
524 return pydoc.text.repr(value)
525 except KeyboardInterrupt:
526 raise
527 except:
528 try:
529 return repr(value)
530 except KeyboardInterrupt:
531 raise
532 except:
533 try:
534 # all still in an except block so we catch
535 # getattr raising
536 name = getattr(value, '__name__', None)
537 if name:
538 # ick, recursion
539 return text_repr(name)
540 klass = getattr(value, '__class__', None)
541 if klass:
542 return '%s instance' % text_repr(klass)
543 except KeyboardInterrupt:
544 raise
545 except:
546 return 'UNRECOVERABLE REPR FAILURE'
547 def eqrepr(value, repr=text_repr): return '=%s' % repr(value)
548 def nullrepr(value, repr=text_repr): return ''
549
550 # meat of the code begins
551 try:
552 etype = etype.__name__
553 except AttributeError:
554 pass
555
556 if self.long_header:
557 # Header with the exception type, python version, and date
558 pyver = 'Python ' + string.split(sys.version)[0] + ': ' + sys.executable
559 date = time.ctime(time.time())
560
561 head = '%s%s%s\n%s%s%s\n%s' % (Colors.topline, '-'*75, ColorsNormal,
562 exc, ' '*(75-len(str(etype))-len(pyver)),
563 pyver, string.rjust(date, 75) )
564 head += "\nA problem occured executing Python code. Here is the sequence of function"\
565 "\ncalls leading up to the error, with the most recent (innermost) call last."
566 else:
567 # Simplified header
568 head = '%s%s%s\n%s%s' % (Colors.topline, '-'*75, ColorsNormal,exc,
569 string.rjust('Traceback (most recent call last)',
570 75 - len(str(etype)) ) )
571 frames = []
572 # Flush cache before calling inspect. This helps alleviate some of the
573 # problems with python 2.3's inspect.py.
574 linecache.checkcache()
575 # Drop topmost frames if requested
576 try:
577 # Try the default getinnerframes and Alex's: Alex's fixes some
578 # problems, but it generates empty tracebacks for console errors
579 # (5 blanks lines) where none should be returned.
580 #records = inspect.getinnerframes(etb, context)[self.tb_offset:]
581 #print 'python records:', records # dbg
582 records = _fixed_getinnerframes(etb, context,self.tb_offset)
583 #print 'alex records:', records # dbg
584 except:
585
586 # FIXME: I've been getting many crash reports from python 2.3
587 # users, traceable to inspect.py. If I can find a small test-case
588 # to reproduce this, I should either write a better workaround or
589 # file a bug report against inspect (if that's the real problem).
590 # So far, I haven't been able to find an isolated example to
591 # reproduce the problem.
592 inspect_error()
593 traceback.print_exc(file=Term.cerr)
594 info('\nUnfortunately, your original traceback can not be constructed.\n')
595 return ''
596
597 # build some color string templates outside these nested loops
598 tpl_link = '%s%%s%s' % (Colors.filenameEm,ColorsNormal)
599 tpl_call = 'in %s%%s%s%%s%s' % (Colors.vName, Colors.valEm,
600 ColorsNormal)
601 tpl_call_fail = 'in %s%%s%s(***failed resolving arguments***)%s' % \
602 (Colors.vName, Colors.valEm, ColorsNormal)
603 tpl_local_var = '%s%%s%s' % (Colors.vName, ColorsNormal)
604 tpl_global_var = '%sglobal%s %s%%s%s' % (Colors.em, ColorsNormal,
605 Colors.vName, ColorsNormal)
606 tpl_name_val = '%%s %s= %%s%s' % (Colors.valEm, ColorsNormal)
607 tpl_line = '%s%%s%s %%s' % (Colors.lineno, ColorsNormal)
608 tpl_line_em = '%s%%s%s %%s%s' % (Colors.linenoEm,Colors.line,
609 ColorsNormal)
610
611 # now, loop over all records printing context and info
612 abspath = os.path.abspath
613 for frame, file, lnum, func, lines, index in records:
614 #print '*** record:',file,lnum,func,lines,index # dbg
615 try:
616 file = file and abspath(file) or '?'
617 except OSError:
618 # if file is '<console>' or something not in the filesystem,
619 # the abspath call will throw an OSError. Just ignore it and
620 # keep the original file string.
621 pass
622 link = tpl_link % file
623 try:
624 args, varargs, varkw, locals = inspect.getargvalues(frame)
625 except:
626 # This can happen due to a bug in python2.3. We should be
627 # able to remove this try/except when 2.4 becomes a
628 # requirement. Bug details at http://python.org/sf/1005466
629 inspect_error()
630 traceback.print_exc(file=Term.cerr)
631 info("\nIPython's exception reporting continues...\n")
632
633 if func == '?':
634 call = ''
635 else:
636 # Decide whether to include variable details or not
637 var_repr = self.include_vars and eqrepr or nullrepr
638 try:
639 call = tpl_call % (func,inspect.formatargvalues(args,
640 varargs, varkw,
641 locals,formatvalue=var_repr))
642 except KeyError:
643 # Very odd crash from inspect.formatargvalues(). The
644 # scenario under which it appeared was a call to
645 # view(array,scale) in NumTut.view.view(), where scale had
646 # been defined as a scalar (it should be a tuple). Somehow
647 # inspect messes up resolving the argument list of view()
648 # and barfs out. At some point I should dig into this one
649 # and file a bug report about it.
650 inspect_error()
651 traceback.print_exc(file=Term.cerr)
652 info("\nIPython's exception reporting continues...\n")
653 call = tpl_call_fail % func
654
655 # Initialize a list of names on the current line, which the
656 # tokenizer below will populate.
657 names = []
658
659 def tokeneater(token_type, token, start, end, line):
660 """Stateful tokeneater which builds dotted names.
661
662 The list of names it appends to (from the enclosing scope) can
663 contain repeated composite names. This is unavoidable, since
664 there is no way to disambguate partial dotted structures until
665 the full list is known. The caller is responsible for pruning
666 the final list of duplicates before using it."""
667
668 # build composite names
669 if token == '.':
670 try:
671 names[-1] += '.'
672 # store state so the next token is added for x.y.z names
673 tokeneater.name_cont = True
674 return
675 except IndexError:
676 pass
677 if token_type == tokenize.NAME and token not in keyword.kwlist:
678 if tokeneater.name_cont:
679 # Dotted names
680 names[-1] += token
681 tokeneater.name_cont = False
682 else:
683 # Regular new names. We append everything, the caller
684 # will be responsible for pruning the list later. It's
685 # very tricky to try to prune as we go, b/c composite
686 # names can fool us. The pruning at the end is easy
687 # to do (or the caller can print a list with repeated
688 # names if so desired.
689 names.append(token)
690 elif token_type == tokenize.NEWLINE:
691 raise IndexError
692 # we need to store a bit of state in the tokenizer to build
693 # dotted names
694 tokeneater.name_cont = False
695
696 def linereader(file=file, lnum=[lnum], getline=linecache.getline):
697 line = getline(file, lnum[0])
698 lnum[0] += 1
699 return line
700
701 # Build the list of names on this line of code where the exception
702 # occurred.
703 try:
704 # This builds the names list in-place by capturing it from the
705 # enclosing scope.
706 tokenize.tokenize(linereader, tokeneater)
707 except IndexError:
708 # signals exit of tokenizer
709 pass
710 except tokenize.TokenError,msg:
711 _m = ("An unexpected error occurred while tokenizing input\n"
712 "The following traceback may be corrupted or invalid\n"
713 "The error message is: %s\n" % msg)
714 error(_m)
715
716 # prune names list of duplicates, but keep the right order
717 unique_names = uniq_stable(names)
718
719 # Start loop over vars
720 lvals = []
721 if self.include_vars:
722 for name_full in unique_names:
723 name_base = name_full.split('.',1)[0]
724 if name_base in frame.f_code.co_varnames:
725 if locals.has_key(name_base):
726 try:
727 value = repr(eval(name_full,locals))
728 except:
729 value = undefined
730 else:
731 value = undefined
732 name = tpl_local_var % name_full
733 else:
734 if frame.f_globals.has_key(name_base):
735 try:
736 value = repr(eval(name_full,frame.f_globals))
737 except:
738 value = undefined
739 else:
740 value = undefined
741 name = tpl_global_var % name_full
742 lvals.append(tpl_name_val % (name,value))
743 if lvals:
744 lvals = '%s%s' % (indent,em_normal.join(lvals))
745 else:
746 lvals = ''
747
748 level = '%s %s\n' % (link,call)
749
750 if index is None:
751 frames.append(level)
752 else:
753 frames.append('%s%s' % (level,''.join(
754 _formatTracebackLines(lnum,index,lines,Colors,lvals,
755 col_scheme))))
756
757 # Get (safely) a string form of the exception info
758 try:
759 etype_str,evalue_str = map(str,(etype,evalue))
760 except:
761 # User exception is improperly defined.
762 etype,evalue = str,sys.exc_info()[:2]
763 etype_str,evalue_str = map(str,(etype,evalue))
764 # ... and format it
765 exception = ['%s%s%s: %s' % (Colors.excName, etype_str,
766 ColorsNormal, evalue_str)]
767 if type(evalue) is types.InstanceType:
768 try:
769 names = [w for w in dir(evalue) if isinstance(w, basestring)]
770 except:
771 # Every now and then, an object with funny inernals blows up
772 # when dir() is called on it. We do the best we can to report
773 # the problem and continue
774 _m = '%sException reporting error (object with broken dir())%s:'
775 exception.append(_m % (Colors.excName,ColorsNormal))
776 etype_str,evalue_str = map(str,sys.exc_info()[:2])
777 exception.append('%s%s%s: %s' % (Colors.excName,etype_str,
778 ColorsNormal, evalue_str))
779 names = []
780 for name in names:
781 value = text_repr(getattr(evalue, name))
782 exception.append('\n%s%s = %s' % (indent, name, value))
783 # return all our info assembled as a single string
784 return '%s\n\n%s\n%s' % (head,'\n'.join(frames),''.join(exception[0]) )
785
786 def debugger(self,force=False):
787 """Call up the pdb debugger if desired, always clean up the tb
788 reference.
789
790 Keywords:
791
792 - force(False): by default, this routine checks the instance call_pdb
793 flag and does not actually invoke the debugger if the flag is false.
794 The 'force' option forces the debugger to activate even if the flag
795 is false.
796
797 If the call_pdb flag is set, the pdb interactive debugger is
798 invoked. In all cases, the self.tb reference to the current traceback
799 is deleted to prevent lingering references which hamper memory
800 management.
801
802 Note that each call to pdb() does an 'import readline', so if your app
803 requires a special setup for the readline completers, you'll have to
804 fix that by hand after invoking the exception handler."""
805
806 if force or self.call_pdb:
807 if self.pdb is None:
808 self.pdb = Debugger.Pdb(
809 self.color_scheme_table.active_scheme_name)
810 # the system displayhook may have changed, restore the original
811 # for pdb
812 dhook = sys.displayhook
813 sys.displayhook = sys.__displayhook__
814 self.pdb.reset()
815 # Find the right frame so we don't pop up inside ipython itself
816 if hasattr(self,'tb'):
817 etb = self.tb
818 else:
819 etb = self.tb = sys.last_traceback
820 while self.tb.tb_next is not None:
821 self.tb = self.tb.tb_next
822 try:
823 if etb and etb.tb_next:
824 etb = etb.tb_next
825 self.pdb.botframe = etb.tb_frame
826 self.pdb.interaction(self.tb.tb_frame, self.tb)
827 finally:
828 sys.displayhook = dhook
829
830 if hasattr(self,'tb'):
831 del self.tb
832
833 def handler(self, info=None):
834 (etype, evalue, etb) = info or sys.exc_info()
835 self.tb = etb
836 Term.cout.flush()
837 Term.cerr.flush()
838 print >> Term.cerr, self.text(etype, evalue, etb)
839
840 # Changed so an instance can just be called as VerboseTB_inst() and print
841 # out the right info on its own.
842 def __call__(self, etype=None, evalue=None, etb=None):
843 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
844 if etb is None:
845 self.handler()
846 else:
847 self.handler((etype, evalue, etb))
848 self.debugger()
849
850 #----------------------------------------------------------------------------
851 class FormattedTB(VerboseTB,ListTB):
852 """Subclass ListTB but allow calling with a traceback.
853
854 It can thus be used as a sys.excepthook for Python > 2.1.
855
856 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
857
858 Allows a tb_offset to be specified. This is useful for situations where
859 one needs to remove a number of topmost frames from the traceback (such as
860 occurs with python programs that themselves execute other python code,
861 like Python shells). """
862
863 def __init__(self, mode = 'Plain', color_scheme='Linux',
864 tb_offset = 0,long_header=0,call_pdb=0,include_vars=0):
865
866 # NEVER change the order of this list. Put new modes at the end:
867 self.valid_modes = ['Plain','Context','Verbose']
868 self.verbose_modes = self.valid_modes[1:3]
869
870 VerboseTB.__init__(self,color_scheme,tb_offset,long_header,
871 call_pdb=call_pdb,include_vars=include_vars)
872 self.set_mode(mode)
873
874 def _extract_tb(self,tb):
875 if tb:
876 return traceback.extract_tb(tb)
877 else:
878 return None
879
880 def text(self, etype, value, tb,context=5,mode=None):
881 """Return formatted traceback.
882
883 If the optional mode parameter is given, it overrides the current
884 mode."""
885
886 if mode is None:
887 mode = self.mode
888 if mode in self.verbose_modes:
889 # verbose modes need a full traceback
890 return VerboseTB.text(self,etype, value, tb,context=5)
891 else:
892 # We must check the source cache because otherwise we can print
893 # out-of-date source code.
894 linecache.checkcache()
895 # Now we can extract and format the exception
896 elist = self._extract_tb(tb)
897 if len(elist) > self.tb_offset:
898 del elist[:self.tb_offset]
899 return ListTB.text(self,etype,value,elist)
900
901 def set_mode(self,mode=None):
902 """Switch to the desired mode.
903
904 If mode is not specified, cycles through the available modes."""
905
906 if not mode:
907 new_idx = ( self.valid_modes.index(self.mode) + 1 ) % \
908 len(self.valid_modes)
909 self.mode = self.valid_modes[new_idx]
910 elif mode not in self.valid_modes:
911 raise ValueError, 'Unrecognized mode in FormattedTB: <'+mode+'>\n'\
912 'Valid modes: '+str(self.valid_modes)
913 else:
914 self.mode = mode
915 # include variable details only in 'Verbose' mode
916 self.include_vars = (self.mode == self.valid_modes[2])
917
918 # some convenient shorcuts
919 def plain(self):
920 self.set_mode(self.valid_modes[0])
921
922 def context(self):
923 self.set_mode(self.valid_modes[1])
924
925 def verbose(self):
926 self.set_mode(self.valid_modes[2])
927
928 #----------------------------------------------------------------------------
929 class AutoFormattedTB(FormattedTB):
930 """A traceback printer which can be called on the fly.
931
932 It will find out about exceptions by itself.
933
934 A brief example:
935
936 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
937 try:
938 ...
939 except:
940 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
941 """
942 def __call__(self,etype=None,evalue=None,etb=None,
943 out=None,tb_offset=None):
944 """Print out a formatted exception traceback.
945
946 Optional arguments:
947 - out: an open file-like object to direct output to.
948
949 - tb_offset: the number of frames to skip over in the stack, on a
950 per-call basis (this overrides temporarily the instance's tb_offset
951 given at initialization time. """
952
953 if out is None:
954 out = Term.cerr
955 Term.cout.flush()
956 out.flush()
957 if tb_offset is not None:
958 tb_offset, self.tb_offset = self.tb_offset, tb_offset
959 print >> out, self.text(etype, evalue, etb)
960 self.tb_offset = tb_offset
961 else:
962 print >> out, self.text(etype, evalue, etb)
963 self.debugger()
964
965 def text(self,etype=None,value=None,tb=None,context=5,mode=None):
966 if etype is None:
967 etype,value,tb = sys.exc_info()
968 self.tb = tb
969 return FormattedTB.text(self,etype,value,tb,context=5,mode=mode)
970
971 #---------------------------------------------------------------------------
972 # A simple class to preserve Nathan's original functionality.
973 class ColorTB(FormattedTB):
974 """Shorthand to initialize a FormattedTB in Linux colors mode."""
975 def __init__(self,color_scheme='Linux',call_pdb=0):
976 FormattedTB.__init__(self,color_scheme=color_scheme,
977 call_pdb=call_pdb)
978
979 #----------------------------------------------------------------------------
980 # module testing (minimal)
981 if __name__ == "__main__":
982 def spam(c, (d, e)):
983 x = c + d
984 y = c * d
985 foo(x, y)
986
987 def foo(a, b, bar=1):
988 eggs(a, b + bar)
989
990 def eggs(f, g, z=globals()):
991 h = f + g
992 i = f - g
993 return h / i
994
995 print ''
996 print '*** Before ***'
997 try:
998 print spam(1, (2, 3))
999 except:
1000 traceback.print_exc()
1001 print ''
1002
1003 handler = ColorTB()
1004 print '*** ColorTB ***'
1005 try:
1006 print spam(1, (2, 3))
1007 except:
1008 apply(handler, sys.exc_info() )
1009 print ''
1010
1011 handler = VerboseTB()
1012 print '*** VerboseTB ***'
1013 try:
1014 print spam(1, (2, 3))
1015 except:
1016 apply(handler, sys.exc_info() )
1017 print ''
1018
@@ -0,0 +1,197 b''
1 # encoding: utf-8
2
3 __docformat__ = "restructuredtext en"
4
5 #-------------------------------------------------------------------------------
6 # Copyright (C) 2008 The IPython Development Team
7 #
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
10 #-------------------------------------------------------------------------------
11
12 #-------------------------------------------------------------------------------
13 # Imports
14 #-------------------------------------------------------------------------------
15
16 import os
17 import sys
18
19
20 # This class is mostly taken from IPython.
21 class InputList(list):
22 """ Class to store user input.
23
24 It's basically a list, but slices return a string instead of a list, thus
25 allowing things like (assuming 'In' is an instance):
26
27 exec In[4:7]
28
29 or
30
31 exec In[5:9] + In[14] + In[21:25]
32 """
33
34 def __getslice__(self, i, j):
35 return ''.join(list.__getslice__(self, i, j))
36
37 def add(self, index, command):
38 """ Add a command to the list with the appropriate index.
39
40 If the index is greater than the current length of the list, empty
41 strings are added in between.
42 """
43
44 length = len(self)
45 if length == index:
46 self.append(command)
47 elif length > index:
48 self[index] = command
49 else:
50 extras = index - length
51 self.extend([''] * extras)
52 self.append(command)
53
54
55 class Bunch(dict):
56 """ A dictionary that exposes its keys as attributes.
57 """
58
59 def __init__(self, *args, **kwds):
60 dict.__init__(self, *args, **kwds)
61 self.__dict__ = self
62
63
64 def esc_quotes(strng):
65 """ Return the input string with single and double quotes escaped out.
66 """
67
68 return strng.replace('"', '\\"').replace("'", "\\'")
69
70 def make_quoted_expr(s):
71 """Return string s in appropriate quotes, using raw string if possible.
72
73 Effectively this turns string: cd \ao\ao\
74 to: r"cd \ao\ao\_"[:-1]
75
76 Note the use of raw string and padding at the end to allow trailing
77 backslash.
78 """
79
80 tail = ''
81 tailpadding = ''
82 raw = ''
83 if "\\" in s:
84 raw = 'r'
85 if s.endswith('\\'):
86 tail = '[:-1]'
87 tailpadding = '_'
88 if '"' not in s:
89 quote = '"'
90 elif "'" not in s:
91 quote = "'"
92 elif '"""' not in s and not s.endswith('"'):
93 quote = '"""'
94 elif "'''" not in s and not s.endswith("'"):
95 quote = "'''"
96 else:
97 # Give up, backslash-escaped string will do
98 return '"%s"' % esc_quotes(s)
99 res = ''.join([raw, quote, s, tailpadding, quote, tail])
100 return res
101
102 # This function is used by ipython in a lot of places to make system calls.
103 # We need it to be slightly different under win32, due to the vagaries of
104 # 'network shares'. A win32 override is below.
105
106 def system_shell(cmd, verbose=False, debug=False, header=''):
107 """ Execute a command in the system shell; always return None.
108
109 Parameters
110 ----------
111 cmd : str
112 The command to execute.
113 verbose : bool
114 If True, print the command to be executed.
115 debug : bool
116 Only print, do not actually execute.
117 header : str
118 Header to print to screen prior to the executed command. No extra
119 newlines are added.
120
121 Description
122 -----------
123 This returns None so it can be conveniently used in interactive loops
124 without getting the return value (typically 0) printed many times.
125 """
126
127 if verbose or debug:
128 print header + cmd
129
130 # Flush stdout so we don't mangle python's buffering.
131 sys.stdout.flush()
132 if not debug:
133 os.system(cmd)
134
135 # Override shell() for win32 to deal with network shares.
136 if os.name in ('nt', 'dos'):
137
138 system_shell_ori = system_shell
139
140 def system_shell(cmd, verbose=False, debug=False, header=''):
141 if os.getcwd().startswith(r"\\"):
142 path = os.getcwd()
143 # Change to c drive (cannot be on UNC-share when issuing os.system,
144 # as cmd.exe cannot handle UNC addresses).
145 os.chdir("c:")
146 # Issue pushd to the UNC-share and then run the command.
147 try:
148 system_shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
149 finally:
150 os.chdir(path)
151 else:
152 system_shell_ori(cmd,verbose,debug,header)
153
154 system_shell.__doc__ = system_shell_ori.__doc__
155
156 def getoutputerror(cmd, verbose=False, debug=False, header='', split=False):
157 """ Executes a command and returns the output.
158
159 Parameters
160 ----------
161 cmd : str
162 The command to execute.
163 verbose : bool
164 If True, print the command to be executed.
165 debug : bool
166 Only print, do not actually execute.
167 header : str
168 Header to print to screen prior to the executed command. No extra
169 newlines are added.
170 split : bool
171 If True, return the output as a list split on newlines.
172
173 """
174
175 if verbose or debug:
176 print header+cmd
177
178 if not cmd:
179 # Return empty lists or strings.
180 if split:
181 return [], []
182 else:
183 return '', ''
184
185 if not debug:
186 # fixme: use subprocess.
187 pin,pout,perr = os.popen3(cmd)
188 tout = pout.read().rstrip()
189 terr = perr.read().rstrip()
190 pin.close()
191 pout.close()
192 perr.close()
193 if split:
194 return tout.split('\n'), terr.split('\n')
195 else:
196 return tout, terr
197
@@ -0,0 +1,87 b''
1 # encoding: utf-8
2
3 """A class that manages the engines connection to the controller."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19 import cPickle as pickle
20
21 from twisted.python import log
22
23 from IPython.kernel.fcutil import find_furl
24 from IPython.kernel.enginefc import IFCEngine
25
26 #-------------------------------------------------------------------------------
27 # The ClientConnector class
28 #-------------------------------------------------------------------------------
29
30 class EngineConnector(object):
31 """Manage an engines connection to a controller.
32
33 This class takes a foolscap `Tub` and provides a `connect_to_controller`
34 method that will use the `Tub` to connect to a controller and register
35 the engine with the controller.
36 """
37
38 def __init__(self, tub):
39 self.tub = tub
40
41 def connect_to_controller(self, engine_service, furl_or_file):
42 """
43 Make a connection to a controller specified by a furl.
44
45 This method takes an `IEngineBase` instance and a foolcap URL and uses
46 the `tub` attribute to make a connection to the controller. The
47 foolscap URL contains all the information needed to connect to the
48 controller, including the ip and port as well as any encryption and
49 authentication information needed for the connection.
50
51 After getting a reference to the controller, this method calls the
52 `register_engine` method of the controller to actually register the
53 engine.
54
55 :Parameters:
56 engine_service : IEngineBase
57 An instance of an `IEngineBase` implementer
58 furl_or_file : str
59 A furl or a filename containing a furl
60 """
61 if not self.tub.running:
62 self.tub.startService()
63 self.engine_service = engine_service
64 self.engine_reference = IFCEngine(self.engine_service)
65 self.furl = find_furl(furl_or_file)
66 d = self.tub.getReference(self.furl)
67 d.addCallbacks(self._register, self._log_failure)
68 return d
69
70 def _log_failure(self, reason):
71 log.err('engine registration failed:')
72 log.err(reason)
73 return reason
74
75 def _register(self, rr):
76 self.remote_ref = rr
77 # Now register myself with the controller
78 desired_id = self.engine_service.id
79 d = self.remote_ref.callRemote('register_engine', self.engine_reference,
80 desired_id, os.getpid(), pickle.dumps(self.engine_service.properties,2))
81 return d.addCallbacks(self._reference_sent, self._log_failure)
82
83 def _reference_sent(self, registration_dict):
84 self.engine_service.id = registration_dict['id']
85 log.msg("engine registration succeeded, got id: %r" % self.engine_service.id)
86 return self.engine_service.id
87
This diff has been collapsed as it changes many lines, (548 lines changed) Show them Hide them
@@ -0,0 +1,548 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_enginepb -*-
3
4 """
5 Expose the IPython EngineService using the Foolscap network protocol.
6
7 Foolscap is a high-performance and secure network protocol.
8 """
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import os, time
23 import cPickle as pickle
24
25 from twisted.python import components, log, failure
26 from twisted.python.failure import Failure
27 from twisted.internet import defer, reactor, threads
28 from twisted.internet.interfaces import IProtocolFactory
29 from zope.interface import Interface, implements, Attribute
30
31 from twisted.internet.base import DelayedCall
32 DelayedCall.debug = True
33
34 from foolscap import Referenceable, DeadReferenceError
35 from foolscap.referenceable import RemoteReference
36
37 from IPython.kernel.pbutil import packageFailure, unpackageFailure
38 from IPython.kernel.util import printer
39 from IPython.kernel.twistedutil import gatherBoth
40 from IPython.kernel import newserialized
41 from IPython.kernel.error import ProtocolError
42 from IPython.kernel import controllerservice
43 from IPython.kernel.controllerservice import IControllerBase
44 from IPython.kernel.engineservice import \
45 IEngineBase, \
46 IEngineQueued, \
47 EngineService, \
48 StrictDict
49 from IPython.kernel.pickleutil import \
50 can, \
51 canDict, \
52 canSequence, \
53 uncan, \
54 uncanDict, \
55 uncanSequence
56
57
58 #-------------------------------------------------------------------------------
59 # The client (Engine) side of things
60 #-------------------------------------------------------------------------------
61
62 # Expose a FC interface to the EngineService
63
64 class IFCEngine(Interface):
65 """An interface that exposes an EngineService over Foolscap.
66
67 The methods in this interface are similar to those from IEngine,
68 but their arguments and return values slightly different to reflect
69 that FC cannot send arbitrary objects. We handle this by pickling/
70 unpickling that the two endpoints.
71
72 If a remote or local exception is raised, the appropriate Failure
73 will be returned instead.
74 """
75 pass
76
77
78 class FCEngineReferenceFromService(Referenceable, object):
79 """Adapt an `IEngineBase` to an `IFCEngine` implementer.
80
81 This exposes an `IEngineBase` to foolscap by adapting it to a
82 `foolscap.Referenceable`.
83
84 See the documentation of the `IEngineBase` methods for more details.
85 """
86
87 implements(IFCEngine)
88
89 def __init__(self, service):
90 assert IEngineBase.providedBy(service), \
91 "IEngineBase is not provided by" + repr(service)
92 self.service = service
93 self.collectors = {}
94
95 def remote_get_id(self):
96 return self.service.id
97
98 def remote_set_id(self, id):
99 self.service.id = id
100
101 def _checkProperties(self, result):
102 dosync = self.service.properties.modified
103 self.service.properties.modified = False
104 return (dosync and pickle.dumps(self.service.properties, 2)), result
105
106 def remote_execute(self, lines):
107 d = self.service.execute(lines)
108 d.addErrback(packageFailure)
109 d.addCallback(self._checkProperties)
110 d.addErrback(packageFailure)
111 #d.addCallback(lambda r: log.msg("Got result: " + str(r)))
112 return d
113
114 #---------------------------------------------------------------------------
115 # Old version of push
116 #---------------------------------------------------------------------------
117
118 def remote_push(self, pNamespace):
119 try:
120 namespace = pickle.loads(pNamespace)
121 except:
122 return defer.fail(failure.Failure()).addErrback(packageFailure)
123 else:
124 return self.service.push(namespace).addErrback(packageFailure)
125
126 #---------------------------------------------------------------------------
127 # pull
128 #---------------------------------------------------------------------------
129
130 def remote_pull(self, keys):
131 d = self.service.pull(keys)
132 d.addCallback(pickle.dumps, 2)
133 d.addErrback(packageFailure)
134 return d
135
136 #---------------------------------------------------------------------------
137 # push/pullFuction
138 #---------------------------------------------------------------------------
139
140 def remote_push_function(self, pNamespace):
141 try:
142 namespace = pickle.loads(pNamespace)
143 except:
144 return defer.fail(failure.Failure()).addErrback(packageFailure)
145 else:
146 # The usage of globals() here is an attempt to bind any pickled functions
147 # to the globals of this module. What we really want is to have it bound
148 # to the globals of the callers module. This will require walking the
149 # stack. BG 10/3/07.
150 namespace = uncanDict(namespace, globals())
151 return self.service.push_function(namespace).addErrback(packageFailure)
152
153 def remote_pull_function(self, keys):
154 d = self.service.pull_function(keys)
155 if len(keys)>1:
156 d.addCallback(canSequence)
157 elif len(keys)==1:
158 d.addCallback(can)
159 d.addCallback(pickle.dumps, 2)
160 d.addErrback(packageFailure)
161 return d
162
163 #---------------------------------------------------------------------------
164 # Other methods
165 #---------------------------------------------------------------------------
166
167 def remote_get_result(self, i=None):
168 return self.service.get_result(i).addErrback(packageFailure)
169
170 def remote_reset(self):
171 return self.service.reset().addErrback(packageFailure)
172
173 def remote_kill(self):
174 return self.service.kill().addErrback(packageFailure)
175
176 def remote_keys(self):
177 return self.service.keys().addErrback(packageFailure)
178
179 #---------------------------------------------------------------------------
180 # push/pull_serialized
181 #---------------------------------------------------------------------------
182
183 def remote_push_serialized(self, pNamespace):
184 try:
185 namespace = pickle.loads(pNamespace)
186 except:
187 return defer.fail(failure.Failure()).addErrback(packageFailure)
188 else:
189 d = self.service.push_serialized(namespace)
190 return d.addErrback(packageFailure)
191
192 def remote_pull_serialized(self, keys):
193 d = self.service.pull_serialized(keys)
194 d.addCallback(pickle.dumps, 2)
195 d.addErrback(packageFailure)
196 return d
197
198 #---------------------------------------------------------------------------
199 # Properties interface
200 #---------------------------------------------------------------------------
201
202 def remote_set_properties(self, pNamespace):
203 try:
204 namespace = pickle.loads(pNamespace)
205 except:
206 return defer.fail(failure.Failure()).addErrback(packageFailure)
207 else:
208 return self.service.set_properties(namespace).addErrback(packageFailure)
209
210 def remote_get_properties(self, keys=None):
211 d = self.service.get_properties(keys)
212 d.addCallback(pickle.dumps, 2)
213 d.addErrback(packageFailure)
214 return d
215
216 def remote_has_properties(self, keys):
217 d = self.service.has_properties(keys)
218 d.addCallback(pickle.dumps, 2)
219 d.addErrback(packageFailure)
220 return d
221
222 def remote_del_properties(self, keys):
223 d = self.service.del_properties(keys)
224 d.addErrback(packageFailure)
225 return d
226
227 def remote_clear_properties(self):
228 d = self.service.clear_properties()
229 d.addErrback(packageFailure)
230 return d
231
232
233 components.registerAdapter(FCEngineReferenceFromService,
234 IEngineBase,
235 IFCEngine)
236
237
238 #-------------------------------------------------------------------------------
239 # Now the server (Controller) side of things
240 #-------------------------------------------------------------------------------
241
242 class EngineFromReference(object):
243 """Adapt a `RemoteReference` to an `IEngineBase` implementing object.
244
245 When an engine connects to a controller, it calls the `register_engine`
246 method of the controller and passes the controller a `RemoteReference` to
247 itself. This class is used to adapt this `RemoteReference` to an object
248 that implements the full `IEngineBase` interface.
249
250 See the documentation of `IEngineBase` for details on the methods.
251 """
252
253 implements(IEngineBase)
254
255 def __init__(self, reference):
256 self.reference = reference
257 self._id = None
258 self._properties = StrictDict()
259 self.currentCommand = None
260
261 def callRemote(self, *args, **kwargs):
262 try:
263 return self.reference.callRemote(*args, **kwargs)
264 except DeadReferenceError:
265 self.notifier()
266 self.stopNotifying(self.notifier)
267 return defer.fail()
268
269 def get_id(self):
270 """Return the Engines id."""
271 return self._id
272
273 def set_id(self, id):
274 """Set the Engines id."""
275 self._id = id
276 return self.callRemote('set_id', id)
277
278 id = property(get_id, set_id)
279
280 def syncProperties(self, r):
281 try:
282 psync, result = r
283 except (ValueError, TypeError):
284 return r
285 else:
286 if psync:
287 log.msg("sync properties")
288 pick = self.checkReturnForFailure(psync)
289 if isinstance(pick, failure.Failure):
290 self.properties = pick
291 return pick
292 else:
293 self.properties = pickle.loads(pick)
294 return result
295
296 def _set_properties(self, dikt):
297 self._properties.clear()
298 self._properties.update(dikt)
299
300 def _get_properties(self):
301 if isinstance(self._properties, failure.Failure):
302 self._properties.raiseException()
303 return self._properties
304
305 properties = property(_get_properties, _set_properties)
306
307 #---------------------------------------------------------------------------
308 # Methods from IEngine
309 #---------------------------------------------------------------------------
310
311 #---------------------------------------------------------------------------
312 # execute
313 #---------------------------------------------------------------------------
314
315 def execute(self, lines):
316 # self._needProperties = True
317 d = self.callRemote('execute', lines)
318 d.addCallback(self.syncProperties)
319 return d.addCallback(self.checkReturnForFailure)
320
321 #---------------------------------------------------------------------------
322 # push
323 #---------------------------------------------------------------------------
324
325 def push(self, namespace):
326 try:
327 package = pickle.dumps(namespace, 2)
328 except:
329 return defer.fail(failure.Failure())
330 else:
331 if isinstance(package, failure.Failure):
332 return defer.fail(package)
333 else:
334 d = self.callRemote('push', package)
335 return d.addCallback(self.checkReturnForFailure)
336
337 #---------------------------------------------------------------------------
338 # pull
339 #---------------------------------------------------------------------------
340
341 def pull(self, keys):
342 d = self.callRemote('pull', keys)
343 d.addCallback(self.checkReturnForFailure)
344 d.addCallback(pickle.loads)
345 return d
346
347 #---------------------------------------------------------------------------
348 # push/pull_function
349 #---------------------------------------------------------------------------
350
351 def push_function(self, namespace):
352 try:
353 package = pickle.dumps(canDict(namespace), 2)
354 except:
355 return defer.fail(failure.Failure())
356 else:
357 if isinstance(package, failure.Failure):
358 return defer.fail(package)
359 else:
360 d = self.callRemote('push_function', package)
361 return d.addCallback(self.checkReturnForFailure)
362
363 def pull_function(self, keys):
364 d = self.callRemote('pull_function', keys)
365 d.addCallback(self.checkReturnForFailure)
366 d.addCallback(pickle.loads)
367 # The usage of globals() here is an attempt to bind any pickled functions
368 # to the globals of this module. What we really want is to have it bound
369 # to the globals of the callers module. This will require walking the
370 # stack. BG 10/3/07.
371 if len(keys)==1:
372 d.addCallback(uncan, globals())
373 elif len(keys)>1:
374 d.addCallback(uncanSequence, globals())
375 return d
376
377 #---------------------------------------------------------------------------
378 # Other methods
379 #---------------------------------------------------------------------------
380
381 def get_result(self, i=None):
382 return self.callRemote('get_result', i).addCallback(self.checkReturnForFailure)
383
384 def reset(self):
385 self._refreshProperties = True
386 d = self.callRemote('reset')
387 d.addCallback(self.syncProperties)
388 return d.addCallback(self.checkReturnForFailure)
389
390 def kill(self):
391 #this will raise pb.PBConnectionLost on success
392 d = self.callRemote('kill')
393 d.addCallback(self.syncProperties)
394 d.addCallback(self.checkReturnForFailure)
395 d.addErrback(self.killBack)
396 return d
397
398 def killBack(self, f):
399 log.msg('filling engine: %s' % f)
400 return None
401
402 def keys(self):
403 return self.callRemote('keys').addCallback(self.checkReturnForFailure)
404
405 #---------------------------------------------------------------------------
406 # Properties methods
407 #---------------------------------------------------------------------------
408
409 def set_properties(self, properties):
410 try:
411 package = pickle.dumps(properties, 2)
412 except:
413 return defer.fail(failure.Failure())
414 else:
415 if isinstance(package, failure.Failure):
416 return defer.fail(package)
417 else:
418 d = self.callRemote('set_properties', package)
419 return d.addCallback(self.checkReturnForFailure)
420 return d
421
422 def get_properties(self, keys=None):
423 d = self.callRemote('get_properties', keys)
424 d.addCallback(self.checkReturnForFailure)
425 d.addCallback(pickle.loads)
426 return d
427
428 def has_properties(self, keys):
429 d = self.callRemote('has_properties', keys)
430 d.addCallback(self.checkReturnForFailure)
431 d.addCallback(pickle.loads)
432 return d
433
434 def del_properties(self, keys):
435 d = self.callRemote('del_properties', keys)
436 d.addCallback(self.checkReturnForFailure)
437 # d.addCallback(pickle.loads)
438 return d
439
440 def clear_properties(self):
441 d = self.callRemote('clear_properties')
442 d.addCallback(self.checkReturnForFailure)
443 return d
444
445 #---------------------------------------------------------------------------
446 # push/pull_serialized
447 #---------------------------------------------------------------------------
448
449 def push_serialized(self, namespace):
450 """Older version of pushSerialize."""
451 try:
452 package = pickle.dumps(namespace, 2)
453 except:
454 return defer.fail(failure.Failure())
455 else:
456 if isinstance(package, failure.Failure):
457 return defer.fail(package)
458 else:
459 d = self.callRemote('push_serialized', package)
460 return d.addCallback(self.checkReturnForFailure)
461
462 def pull_serialized(self, keys):
463 d = self.callRemote('pull_serialized', keys)
464 d.addCallback(self.checkReturnForFailure)
465 d.addCallback(pickle.loads)
466 return d
467
468 #---------------------------------------------------------------------------
469 # Misc
470 #---------------------------------------------------------------------------
471
472 def checkReturnForFailure(self, r):
473 """See if a returned value is a pickled Failure object.
474
475 To distinguish between general pickled objects and pickled Failures, the
476 other side should prepend the string FAILURE: to any pickled Failure.
477 """
478 return unpackageFailure(r)
479
480
481 components.registerAdapter(EngineFromReference,
482 RemoteReference,
483 IEngineBase)
484
485
486 #-------------------------------------------------------------------------------
487 # Now adapt an IControllerBase to incoming FC connections
488 #-------------------------------------------------------------------------------
489
490
491 class IFCControllerBase(Interface):
492 """
493 Interface that tells how an Engine sees a Controller.
494
495 In our architecture, the Controller listens for Engines to connect
496 and register. This interface defines that registration method as it is
497 exposed over the Foolscap network protocol
498 """
499
500 def remote_register_engine(self, engineReference, id=None, pid=None, pproperties=None):
501 """
502 Register new engine on the controller.
503
504 Engines must call this upon connecting to the controller if they
505 want to do work for the controller.
506
507 See the documentation of `IControllerCore` for more details.
508 """
509
510
511 class FCRemoteEngineRefFromService(Referenceable):
512 """
513 Adapt an `IControllerBase` to an `IFCControllerBase`.
514 """
515
516 implements(IFCControllerBase)
517
518 def __init__(self, service):
519 assert IControllerBase.providedBy(service), \
520 "IControllerBase is not provided by " + repr(service)
521 self.service = service
522
523 def remote_register_engine(self, engine_reference, id=None, pid=None, pproperties=None):
524 # First adapt the engine_reference to a basic non-queued engine
525 engine = IEngineBase(engine_reference)
526 if pproperties:
527 engine.properties = pickle.loads(pproperties)
528 # Make it an IQueuedEngine before registration
529 remote_engine = IEngineQueued(engine)
530 # Get the ip/port of the remote side
531 peer_address = engine_reference.tracker.broker.transport.getPeer()
532 ip = peer_address.host
533 port = peer_address.port
534 reg_dict = self.service.register_engine(remote_engine, id, ip, port, pid)
535 # Now setup callback for disconnect and unregistering the engine
536 def notify(*args):
537 return self.service.unregister_engine(reg_dict['id'])
538 engine_reference.tracker.broker.notifyOnDisconnect(notify)
539
540 engine.notifier = notify
541 engine.stopNotifying = engine_reference.tracker.broker.dontNotifyOnDisconnect
542
543 return reg_dict
544
545
546 components.registerAdapter(FCRemoteEngineRefFromService,
547 IControllerBase,
548 IFCControllerBase)
This diff has been collapsed as it changes many lines, (864 lines changed) Show them Hide them
@@ -0,0 +1,864 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.tests.test_engineservice -*-
3
4 """A Twisted Service Representation of the IPython core.
5
6 The IPython Core exposed to the network is called the Engine. Its
7 representation in Twisted in the EngineService. Interfaces and adapters
8 are used to abstract out the details of the actual network protocol used.
9 The EngineService is an Engine that knows nothing about the actual protocol
10 used.
11
12 The EngineService is exposed with various network protocols in modules like:
13
14 enginepb.py
15 enginevanilla.py
16
17 As of 12/12/06 the classes in this module have been simplified greatly. It was
18 felt that we had over-engineered things. To improve the maintainability of the
19 code we have taken out the ICompleteEngine interface and the completeEngine
20 method that automatically added methods to engines.
21
22 """
23
24 __docformat__ = "restructuredtext en"
25
26 #-------------------------------------------------------------------------------
27 # Copyright (C) 2008 The IPython Development Team
28 #
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
31 #-------------------------------------------------------------------------------
32
33 #-------------------------------------------------------------------------------
34 # Imports
35 #-------------------------------------------------------------------------------
36
37 import os, sys, copy
38 import cPickle as pickle
39 from new import instancemethod
40
41 from twisted.application import service
42 from twisted.internet import defer, reactor
43 from twisted.python import log, failure, components
44 import zope.interface as zi
45
46 from IPython.kernel.core.interpreter import Interpreter
47 from IPython.kernel import newserialized, error, util
48 from IPython.kernel.util import printer
49 from IPython.kernel.twistedutil import gatherBoth, DeferredList
50 from IPython.kernel import codeutil
51
52
53 #-------------------------------------------------------------------------------
54 # Interface specification for the Engine
55 #-------------------------------------------------------------------------------
56
57 class IEngineCore(zi.Interface):
58 """The minimal required interface for the IPython Engine.
59
60 This interface provides a formal specification of the IPython core.
61 All these methods should return deferreds regardless of what side of a
62 network connection they are on.
63
64 In general, this class simply wraps a shell class and wraps its return
65 values as Deferred objects. If the underlying shell class method raises
66 an exception, this class should convert it to a twisted.failure.Failure
67 that will be propagated along the Deferred's errback chain.
68
69 In addition, Failures are aggressive. By this, we mean that if a method
70 is performing multiple actions (like pulling multiple object) if any
71 single one fails, the entire method will fail with that Failure. It is
72 all or nothing.
73 """
74
75 id = zi.interface.Attribute("the id of the Engine object")
76 properties = zi.interface.Attribute("A dict of properties of the Engine")
77
78 def execute(lines):
79 """Execute lines of Python code.
80
81 Returns a dictionary with keys (id, number, stdin, stdout, stderr)
82 upon success.
83
84 Returns a failure object if the execution of lines raises an exception.
85 """
86
87 def push(namespace):
88 """Push dict namespace into the user's namespace.
89
90 Returns a deferred to None or a failure.
91 """
92
93 def pull(keys):
94 """Pulls values out of the user's namespace by keys.
95
96 Returns a deferred to a tuple objects or a single object.
97
98 Raises NameError if any one of objects doess not exist.
99 """
100
101 def push_function(namespace):
102 """Push a dict of key, function pairs into the user's namespace.
103
104 Returns a deferred to None or a failure."""
105
106 def pull_function(keys):
107 """Pulls functions out of the user's namespace by keys.
108
109 Returns a deferred to a tuple of functions or a single function.
110
111 Raises NameError if any one of the functions does not exist.
112 """
113
114 def get_result(i=None):
115 """Get the stdin/stdout/stderr of command i.
116
117 Returns a deferred to a dict with keys
118 (id, number, stdin, stdout, stderr).
119
120 Raises IndexError if command i does not exist.
121 Raises TypeError if i in not an int.
122 """
123
124 def reset():
125 """Reset the shell.
126
127 This clears the users namespace. Won't cause modules to be
128 reloaded. Should also re-initialize certain variables like id.
129 """
130
131 def kill():
132 """Kill the engine by stopping the reactor."""
133
134 def keys():
135 """Return the top level variables in the users namspace.
136
137 Returns a deferred to a dict."""
138
139
140 class IEngineSerialized(zi.Interface):
141 """Push/Pull methods that take Serialized objects.
142
143 All methods should return deferreds.
144 """
145
146 def push_serialized(namespace):
147 """Push a dict of keys and Serialized objects into the user's namespace."""
148
149 def pull_serialized(keys):
150 """Pull objects by key from the user's namespace as Serialized.
151
152 Returns a list of or one Serialized.
153
154 Raises NameError is any one of the objects does not exist.
155 """
156
157
158 class IEngineProperties(zi.Interface):
159 """Methods for access to the properties object of an Engine"""
160
161 properties = zi.Attribute("A StrictDict object, containing the properties")
162
163 def set_properties(properties):
164 """set properties by key and value"""
165
166 def get_properties(keys=None):
167 """get a list of properties by `keys`, if no keys specified, get all"""
168
169 def del_properties(keys):
170 """delete properties by `keys`"""
171
172 def has_properties(keys):
173 """get a list of bool values for whether `properties` has `keys`"""
174
175 def clear_properties():
176 """clear the properties dict"""
177
178 class IEngineBase(IEngineCore, IEngineSerialized, IEngineProperties):
179 """The basic engine interface that EngineService will implement.
180
181 This exists so it is easy to specify adapters that adapt to and from the
182 API that the basic EngineService implements.
183 """
184 pass
185
186 class IEngineQueued(IEngineBase):
187 """Interface for adding a queue to an IEngineBase.
188
189 This interface extends the IEngineBase interface to add methods for managing
190 the engine's queue. The implicit details of this interface are that the
191 execution of all methods declared in IEngineBase should appropriately be
192 put through a queue before execution.
193
194 All methods should return deferreds.
195 """
196
197 def clear_queue():
198 """Clear the queue."""
199
200 def queue_status():
201 """Get the queued and pending commands in the queue."""
202
203 def register_failure_observer(obs):
204 """Register an observer of pending Failures.
205
206 The observer must implement IFailureObserver.
207 """
208
209 def unregister_failure_observer(obs):
210 """Unregister an observer of pending Failures."""
211
212
213 class IEngineThreaded(zi.Interface):
214 """A place holder for threaded commands.
215
216 All methods should return deferreds.
217 """
218 pass
219
220
221 #-------------------------------------------------------------------------------
222 # Functions and classes to implement the EngineService
223 #-------------------------------------------------------------------------------
224
225
226 class StrictDict(dict):
227 """This is a strict copying dictionary for use as the interface to the
228 properties of an Engine.
229 :IMPORTANT:
230 This object copies the values you set to it, and returns copies to you
231 when you request them. The only way to change properties os explicitly
232 through the setitem and getitem of the dictionary interface.
233 Example:
234 >>> e = kernel.get_engine(id)
235 >>> L = someList
236 >>> e.properties['L'] = L
237 >>> L == e.properties['L']
238 ... True
239 >>> L.append(something Else)
240 >>> L == e.properties['L']
241 ... False
242
243 getitem copies, so calls to methods of objects do not affect the
244 properties, as in the following example:
245 >>> e.properties[1] = range(2)
246 >>> print e.properties[1]
247 ... [0, 1]
248 >>> e.properties[1].append(2)
249 >>> print e.properties[1]
250 ... [0, 1]
251
252 """
253 def __init__(self, *args, **kwargs):
254 dict.__init__(self, *args, **kwargs)
255 self.modified = True
256
257 def __getitem__(self, key):
258 return copy.deepcopy(dict.__getitem__(self, key))
259
260 def __setitem__(self, key, value):
261 # check if this entry is valid for transport around the network
262 # and copying
263 try:
264 pickle.dumps(key, 2)
265 pickle.dumps(value, 2)
266 newvalue = copy.deepcopy(value)
267 except:
268 raise error.InvalidProperty(value)
269 dict.__setitem__(self, key, newvalue)
270 self.modified = True
271
272 def __delitem__(self, key):
273 dict.__delitem__(self, key)
274 self.modified = True
275
276 def update(self, dikt):
277 for k,v in dikt.iteritems():
278 self[k] = v
279
280 def pop(self, key):
281 self.modified = True
282 return dict.pop(self, key)
283
284 def popitem(self):
285 self.modified = True
286 return dict.popitem(self)
287
288 def clear(self):
289 self.modified = True
290 dict.clear(self)
291
292 def subDict(self, *keys):
293 d = {}
294 for key in keys:
295 d[key] = self[key]
296 return d
297
298
299
300 class EngineAPI(object):
301 """This is the object through which the user can edit the `properties`
302 attribute of an Engine.
303 The Engine Properties object copies all object in and out of itself.
304 See the EngineProperties object for details.
305 """
306 _fix=False
307 def __init__(self, id):
308 self.id = id
309 self.properties = StrictDict()
310 self._fix=True
311
312 def __setattr__(self, k,v):
313 if self._fix:
314 raise error.KernelError("I am protected!")
315 else:
316 object.__setattr__(self, k, v)
317
318 def __delattr__(self, key):
319 raise error.KernelError("I am protected!")
320
321
322 _apiDict = {}
323
324 def get_engine(id):
325 """Get the Engine API object, whcih currently just provides the properties
326 object, by ID"""
327 global _apiDict
328 if not _apiDict.get(id):
329 _apiDict[id] = EngineAPI(id)
330 return _apiDict[id]
331
332 def drop_engine(id):
333 """remove an engine"""
334 global _apiDict
335 if _apiDict.has_key(id):
336 del _apiDict[id]
337
338 class EngineService(object, service.Service):
339 """Adapt a IPython shell into a IEngine implementing Twisted Service."""
340
341 zi.implements(IEngineBase)
342 name = 'EngineService'
343
344 def __init__(self, shellClass=Interpreter, mpi=None):
345 """Create an EngineService.
346
347 shellClass: something that implements IInterpreter or core1
348 mpi: an mpi module that has rank and size attributes
349 """
350 self.shellClass = shellClass
351 self.shell = self.shellClass()
352 self.mpi = mpi
353 self.id = None
354 self.properties = get_engine(self.id).properties
355 if self.mpi is not None:
356 log.msg("MPI started with rank = %i and size = %i" %
357 (self.mpi.rank, self.mpi.size))
358 self.id = self.mpi.rank
359 self._seedNamespace()
360
361 # Make id a property so that the shell can get the updated id
362
363 def _setID(self, id):
364 self._id = id
365 self.properties = get_engine(id).properties
366 self.shell.push({'id': id})
367
368 def _getID(self):
369 return self._id
370
371 id = property(_getID, _setID)
372
373 def _seedNamespace(self):
374 self.shell.push({'mpi': self.mpi, 'id' : self.id})
375
376 def executeAndRaise(self, msg, callable, *args, **kwargs):
377 """Call a method of self.shell and wrap any exception."""
378 d = defer.Deferred()
379 try:
380 result = callable(*args, **kwargs)
381 except:
382 # This gives the following:
383 # et=exception class
384 # ev=exception class instance
385 # tb=traceback object
386 et,ev,tb = sys.exc_info()
387 # This call adds attributes to the exception value
388 et,ev,tb = self.shell.formatTraceback(et,ev,tb,msg)
389 # Add another attribute
390 ev._ipython_engine_info = msg
391 f = failure.Failure(ev,et,None)
392 d.errback(f)
393 else:
394 d.callback(result)
395
396 return d
397
398 # The IEngine methods. See the interface for documentation.
399
400 def execute(self, lines):
401 msg = {'engineid':self.id,
402 'method':'execute',
403 'args':[lines]}
404 d = self.executeAndRaise(msg, self.shell.execute, lines)
405 d.addCallback(self.addIDToResult)
406 return d
407
408 def addIDToResult(self, result):
409 result['id'] = self.id
410 return result
411
412 def push(self, namespace):
413 msg = {'engineid':self.id,
414 'method':'push',
415 'args':[repr(namespace.keys())]}
416 d = self.executeAndRaise(msg, self.shell.push, namespace)
417 return d
418
419 def pull(self, keys):
420 msg = {'engineid':self.id,
421 'method':'pull',
422 'args':[repr(keys)]}
423 d = self.executeAndRaise(msg, self.shell.pull, keys)
424 return d
425
426 def push_function(self, namespace):
427 msg = {'engineid':self.id,
428 'method':'push_function',
429 'args':[repr(namespace.keys())]}
430 d = self.executeAndRaise(msg, self.shell.push_function, namespace)
431 return d
432
433 def pull_function(self, keys):
434 msg = {'engineid':self.id,
435 'method':'pull_function',
436 'args':[repr(keys)]}
437 d = self.executeAndRaise(msg, self.shell.pull_function, keys)
438 return d
439
440 def get_result(self, i=None):
441 msg = {'engineid':self.id,
442 'method':'get_result',
443 'args':[repr(i)]}
444 d = self.executeAndRaise(msg, self.shell.getCommand, i)
445 d.addCallback(self.addIDToResult)
446 return d
447
448 def reset(self):
449 msg = {'engineid':self.id,
450 'method':'reset',
451 'args':[]}
452 del self.shell
453 self.shell = self.shellClass()
454 self.properties.clear()
455 d = self.executeAndRaise(msg, self._seedNamespace)
456 return d
457
458 def kill(self):
459 drop_engine(self.id)
460 try:
461 reactor.stop()
462 except RuntimeError:
463 log.msg('The reactor was not running apparently.')
464 return defer.fail()
465 else:
466 return defer.succeed(None)
467
468 def keys(self):
469 """Return a list of variables names in the users top level namespace.
470
471 This used to return a dict of all the keys/repr(values) in the
472 user's namespace. This was too much info for the ControllerService
473 to handle so it is now just a list of keys.
474 """
475
476 remotes = []
477 for k in self.shell.user_ns.iterkeys():
478 if k not in ['__name__', '_ih', '_oh', '__builtins__',
479 'In', 'Out', '_', '__', '___', '__IP', 'input', 'raw_input']:
480 remotes.append(k)
481 return defer.succeed(remotes)
482
483 def set_properties(self, properties):
484 msg = {'engineid':self.id,
485 'method':'set_properties',
486 'args':[repr(properties.keys())]}
487 return self.executeAndRaise(msg, self.properties.update, properties)
488
489 def get_properties(self, keys=None):
490 msg = {'engineid':self.id,
491 'method':'get_properties',
492 'args':[repr(keys)]}
493 if keys is None:
494 keys = self.properties.keys()
495 return self.executeAndRaise(msg, self.properties.subDict, *keys)
496
497 def _doDel(self, keys):
498 for key in keys:
499 del self.properties[key]
500
501 def del_properties(self, keys):
502 msg = {'engineid':self.id,
503 'method':'del_properties',
504 'args':[repr(keys)]}
505 return self.executeAndRaise(msg, self._doDel, keys)
506
507 def _doHas(self, keys):
508 return [self.properties.has_key(key) for key in keys]
509
510 def has_properties(self, keys):
511 msg = {'engineid':self.id,
512 'method':'has_properties',
513 'args':[repr(keys)]}
514 return self.executeAndRaise(msg, self._doHas, keys)
515
516 def clear_properties(self):
517 msg = {'engineid':self.id,
518 'method':'clear_properties',
519 'args':[]}
520 return self.executeAndRaise(msg, self.properties.clear)
521
522 def push_serialized(self, sNamespace):
523 msg = {'engineid':self.id,
524 'method':'push_serialized',
525 'args':[repr(sNamespace.keys())]}
526 ns = {}
527 for k,v in sNamespace.iteritems():
528 try:
529 unserialized = newserialized.IUnSerialized(v)
530 ns[k] = unserialized.getObject()
531 except:
532 return defer.fail()
533 return self.executeAndRaise(msg, self.shell.push, ns)
534
535 def pull_serialized(self, keys):
536 msg = {'engineid':self.id,
537 'method':'pull_serialized',
538 'args':[repr(keys)]}
539 if isinstance(keys, str):
540 keys = [keys]
541 if len(keys)==1:
542 d = self.executeAndRaise(msg, self.shell.pull, keys)
543 d.addCallback(newserialized.serialize)
544 return d
545 elif len(keys)>1:
546 d = self.executeAndRaise(msg, self.shell.pull, keys)
547 @d.addCallback
548 def packThemUp(values):
549 serials = []
550 for v in values:
551 try:
552 serials.append(newserialized.serialize(v))
553 except:
554 return defer.fail(failure.Failure())
555 return serials
556 return packThemUp
557
558
559 def queue(methodToQueue):
560 def queuedMethod(this, *args, **kwargs):
561 name = methodToQueue.__name__
562 return this.submitCommand(Command(name, *args, **kwargs))
563 return queuedMethod
564
565 class QueuedEngine(object):
566 """Adapt an IEngineBase to an IEngineQueued by wrapping it.
567
568 The resulting object will implement IEngineQueued which extends
569 IEngineCore which extends (IEngineBase, IEngineSerialized).
570
571 This seems like the best way of handling it, but I am not sure. The
572 other option is to have the various base interfaces be used like
573 mix-in intefaces. The problem I have with this is adpatation is
574 more difficult and complicated because there can be can multiple
575 original and final Interfaces.
576 """
577
578 zi.implements(IEngineQueued)
579
580 def __init__(self, engine):
581 """Create a QueuedEngine object from an engine
582
583 engine: An implementor of IEngineCore and IEngineSerialized
584 keepUpToDate: whether to update the remote status when the
585 queue is empty. Defaults to False.
586 """
587
588 # This is the right way to do these tests rather than
589 # IEngineCore in list(zi.providedBy(engine)) which will only
590 # picks of the interfaces that are directly declared by engine.
591 assert IEngineBase.providedBy(engine), \
592 "engine passed to QueuedEngine doesn't provide IEngineBase"
593
594 self.engine = engine
595 self.id = engine.id
596 self.queued = []
597 self.history = {}
598 self.engineStatus = {}
599 self.currentCommand = None
600 self.failureObservers = []
601
602 def _get_properties(self):
603 return self.engine.properties
604
605 properties = property(_get_properties, lambda self, _: None)
606 # Queue management methods. You should not call these directly
607
608 def submitCommand(self, cmd):
609 """Submit command to queue."""
610
611 d = defer.Deferred()
612 cmd.setDeferred(d)
613 if self.currentCommand is not None:
614 if self.currentCommand.finished:
615 # log.msg("Running command immediately: %r" % cmd)
616 self.currentCommand = cmd
617 self.runCurrentCommand()
618 else: # command is still running
619 # log.msg("Command is running: %r" % self.currentCommand)
620 # log.msg("Queueing: %r" % cmd)
621 self.queued.append(cmd)
622 else:
623 # log.msg("No current commands, running: %r" % cmd)
624 self.currentCommand = cmd
625 self.runCurrentCommand()
626 return d
627
628 def runCurrentCommand(self):
629 """Run current command."""
630
631 cmd = self.currentCommand
632 f = getattr(self.engine, cmd.remoteMethod, None)
633 if f:
634 d = f(*cmd.args, **cmd.kwargs)
635 if cmd.remoteMethod is 'execute':
636 d.addCallback(self.saveResult)
637 d.addCallback(self.finishCommand)
638 d.addErrback(self.abortCommand)
639 else:
640 return defer.fail(AttributeError(cmd.remoteMethod))
641
642 def _flushQueue(self):
643 """Pop next command in queue and run it."""
644
645 if len(self.queued) > 0:
646 self.currentCommand = self.queued.pop(0)
647 self.runCurrentCommand()
648
649 def saveResult(self, result):
650 """Put the result in the history."""
651 self.history[result['number']] = result
652 return result
653
654 def finishCommand(self, result):
655 """Finish currrent command."""
656
657 # The order of these commands is absolutely critical.
658 self.currentCommand.handleResult(result)
659 self.currentCommand.finished = True
660 self._flushQueue()
661 return result
662
663 def abortCommand(self, reason):
664 """Abort current command.
665
666 This eats the Failure but first passes it onto the Deferred that the
667 user has.
668
669 It also clear out the queue so subsequence commands don't run.
670 """
671
672 # The order of these 3 commands is absolutely critical. The currentCommand
673 # must first be marked as finished BEFORE the queue is cleared and before
674 # the current command is sent the failure.
675 # Also, the queue must be cleared BEFORE the current command is sent the Failure
676 # otherwise the errback chain could trigger new commands to be added to the
677 # queue before we clear it. We should clear ONLY the commands that were in
678 # the queue when the error occured.
679 self.currentCommand.finished = True
680 s = "%r %r %r" % (self.currentCommand.remoteMethod, self.currentCommand.args, self.currentCommand.kwargs)
681 self.clear_queue(msg=s)
682 self.currentCommand.handleError(reason)
683
684 return None
685
686 #---------------------------------------------------------------------------
687 # IEngineCore methods
688 #---------------------------------------------------------------------------
689
690 @queue
691 def execute(self, lines):
692 pass
693
694 @queue
695 def push(self, namespace):
696 pass
697
698 @queue
699 def pull(self, keys):
700 pass
701
702 @queue
703 def push_function(self, namespace):
704 pass
705
706 @queue
707 def pull_function(self, keys):
708 pass
709
710 def get_result(self, i=None):
711 if i is None:
712 i = max(self.history.keys()+[None])
713
714 cmd = self.history.get(i, None)
715 # Uncomment this line to disable chaching of results
716 #cmd = None
717 if cmd is None:
718 return self.submitCommand(Command('get_result', i))
719 else:
720 return defer.succeed(cmd)
721
722 def reset(self):
723 self.clear_queue()
724 self.history = {} # reset the cache - I am not sure we should do this
725 return self.submitCommand(Command('reset'))
726
727 def kill(self):
728 self.clear_queue()
729 return self.submitCommand(Command('kill'))
730
731 @queue
732 def keys(self):
733 pass
734
735 #---------------------------------------------------------------------------
736 # IEngineSerialized methods
737 #---------------------------------------------------------------------------
738
739 @queue
740 def push_serialized(self, namespace):
741 pass
742
743 @queue
744 def pull_serialized(self, keys):
745 pass
746
747 #---------------------------------------------------------------------------
748 # IEngineProperties methods
749 #---------------------------------------------------------------------------
750
751 @queue
752 def set_properties(self, namespace):
753 pass
754
755 @queue
756 def get_properties(self, keys=None):
757 pass
758
759 @queue
760 def del_properties(self, keys):
761 pass
762
763 @queue
764 def has_properties(self, keys):
765 pass
766
767 @queue
768 def clear_properties(self):
769 pass
770
771 #---------------------------------------------------------------------------
772 # IQueuedEngine methods
773 #---------------------------------------------------------------------------
774
775 def clear_queue(self, msg=''):
776 """Clear the queue, but doesn't cancel the currently running commmand."""
777
778 for cmd in self.queued:
779 cmd.deferred.errback(failure.Failure(error.QueueCleared(msg)))
780 self.queued = []
781 return defer.succeed(None)
782
783 def queue_status(self):
784 if self.currentCommand is not None:
785 if self.currentCommand.finished:
786 pending = repr(None)
787 else:
788 pending = repr(self.currentCommand)
789 else:
790 pending = repr(None)
791 dikt = {'queue':map(repr,self.queued), 'pending':pending}
792 return defer.succeed(dikt)
793
794 def register_failure_observer(self, obs):
795 self.failureObservers.append(obs)
796
797 def unregister_failure_observer(self, obs):
798 self.failureObservers.remove(obs)
799
800
801 # Now register QueuedEngine as an adpater class that makes an IEngineBase into a
802 # IEngineQueued.
803 components.registerAdapter(QueuedEngine, IEngineBase, IEngineQueued)
804
805
806 class Command(object):
807 """A command object that encapslates queued commands.
808
809 This class basically keeps track of a command that has been queued
810 in a QueuedEngine. It manages the deferreds and hold the method to be called
811 and the arguments to that method.
812 """
813
814
815 def __init__(self, remoteMethod, *args, **kwargs):
816 """Build a new Command object."""
817
818 self.remoteMethod = remoteMethod
819 self.args = args
820 self.kwargs = kwargs
821 self.finished = False
822
823 def setDeferred(self, d):
824 """Sets the deferred attribute of the Command."""
825
826 self.deferred = d
827
828 def __repr__(self):
829 if not self.args:
830 args = ''
831 else:
832 args = str(self.args)[1:-2] #cut off (...,)
833 for k,v in self.kwargs.iteritems():
834 if args:
835 args += ', '
836 args += '%s=%r' %(k,v)
837 return "%s(%s)" %(self.remoteMethod, args)
838
839 def handleResult(self, result):
840 """When the result is ready, relay it to self.deferred."""
841
842 self.deferred.callback(result)
843
844 def handleError(self, reason):
845 """When an error has occured, relay it to self.deferred."""
846
847 self.deferred.errback(reason)
848
849 class ThreadedEngineService(EngineService):
850
851 zi.implements(IEngineBase)
852
853 def __init__(self, shellClass=Interpreter, mpi=None):
854 EngineService.__init__(self, shellClass, mpi)
855 # Only import this if we are going to use this class
856 from twisted.internet import threads
857
858 def execute(self, lines):
859 msg = """engine: %r
860 method: execute(lines)
861 lines = %s""" % (self.id, lines)
862 d = threads.deferToThread(self.executeAndRaise, msg, self.shell.execute, lines)
863 d.addCallback(self.addIDToResult)
864 return d
@@ -0,0 +1,185 b''
1 # encoding: utf-8
2
3 """Classes and functions for kernel related errors and exceptions."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 from IPython.kernel.core import error
19 from twisted.python import failure
20
21 #-------------------------------------------------------------------------------
22 # Error classes
23 #-------------------------------------------------------------------------------
24
25 class KernelError(error.IPythonError):
26 pass
27
28 class NotDefined(KernelError):
29 def __init__(self, name):
30 self.name = name
31 self.args = (name,)
32
33 def __repr__(self):
34 return '<NotDefined: %s>' % self.name
35
36 __str__ = __repr__
37
38 class QueueCleared(KernelError):
39 pass
40
41 class IdInUse(KernelError):
42 pass
43
44 class ProtocolError(KernelError):
45 pass
46
47 class ConnectionError(KernelError):
48 pass
49
50 class InvalidEngineID(KernelError):
51 pass
52
53 class NoEnginesRegistered(KernelError):
54 pass
55
56 class InvalidClientID(KernelError):
57 pass
58
59 class InvalidDeferredID(KernelError):
60 pass
61
62 class SerializationError(KernelError):
63 pass
64
65 class MessageSizeError(KernelError):
66 pass
67
68 class PBMessageSizeError(MessageSizeError):
69 pass
70
71 class ResultNotCompleted(KernelError):
72 pass
73
74 class ResultAlreadyRetrieved(KernelError):
75 pass
76
77 class ClientError(KernelError):
78 pass
79
80 class TaskAborted(KernelError):
81 pass
82
83 class TaskTimeout(KernelError):
84 pass
85
86 class NotAPendingResult(KernelError):
87 pass
88
89 class UnpickleableException(KernelError):
90 pass
91
92 class AbortedPendingDeferredError(KernelError):
93 pass
94
95 class InvalidProperty(KernelError):
96 pass
97
98 class MissingBlockArgument(KernelError):
99 pass
100
101 class StopLocalExecution(KernelError):
102 pass
103
104 class SecurityError(KernelError):
105 pass
106
107 class CompositeError(KernelError):
108 def __init__(self, message, elist):
109 Exception.__init__(self, *(message, elist))
110 self.message = message
111 self.elist = elist
112
113 def _get_engine_str(self, ev):
114 try:
115 ei = ev._ipython_engine_info
116 except AttributeError:
117 return '[Engine Exception]'
118 else:
119 return '[%i:%s]: ' % (ei['engineid'], ei['method'])
120
121 def _get_traceback(self, ev):
122 try:
123 tb = ev._ipython_traceback_text
124 except AttributeError:
125 return 'No traceback available'
126 else:
127 return tb
128
129 def __str__(self):
130 s = str(self.message)
131 for et, ev, etb in self.elist:
132 engine_str = self._get_engine_str(ev)
133 s = s + '\n' + engine_str + str(et.__name__) + ': ' + str(ev)
134 return s
135
136 def print_tracebacks(self, excid=None):
137 if excid is None:
138 for (et,ev,etb) in self.elist:
139 print self._get_engine_str(ev)
140 print self._get_traceback(ev)
141 print
142 else:
143 try:
144 et,ev,etb = self.elist[excid]
145 except:
146 raise IndexError("an exception with index %i does not exist"%excid)
147 else:
148 print self._get_engine_str(ev)
149 print self._get_traceback(ev)
150
151 def raise_exception(self, excid=0):
152 try:
153 et,ev,etb = self.elist[excid]
154 except:
155 raise IndexError("an exception with index %i does not exist"%excid)
156 else:
157 raise et, ev, etb
158
159 def collect_exceptions(rlist, method):
160 elist = []
161 for r in rlist:
162 if isinstance(r, failure.Failure):
163 r.cleanFailure()
164 et, ev, etb = r.type, r.value, r.tb
165 # Sometimes we could have CompositeError in our list. Just take
166 # the errors out of them and put them in our new list. This
167 # has the effect of flattening lists of CompositeErrors into one
168 # CompositeError
169 if et==CompositeError:
170 for e in ev.elist:
171 elist.append(e)
172 else:
173 elist.append((et, ev, etb))
174 if len(elist)==0:
175 return rlist
176 else:
177 msg = "one or more exceptions from call to method: %s" % (method)
178 # This silliness is needed so the debugger has access to the exception
179 # instance (e in this case)
180 try:
181 raise CompositeError(msg, elist)
182 except CompositeError, e:
183 raise e
184
185
@@ -0,0 +1,69 b''
1 # encoding: utf-8
2
3 """Foolscap related utilities."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import os
19
20 from foolscap import Tub, UnauthenticatedTub
21
22 def check_furl_file_security(furl_file, secure):
23 """Remove the old furl_file if changing security modes."""
24
25 if os.path.isfile(furl_file):
26 f = open(furl_file, 'r')
27 oldfurl = f.read().strip()
28 f.close()
29 if (oldfurl.startswith('pb://') and not secure) or (oldfurl.startswith('pbu://') and secure):
30 os.remove(furl_file)
31
32 def is_secure(furl):
33 if is_valid(furl):
34 if furl.startswith("pb://"):
35 return True
36 elif furl.startswith("pbu://"):
37 return False
38 else:
39 raise ValueError("invalid furl: %s" % furl)
40
41 def is_valid(furl):
42 if isinstance(furl, str):
43 if furl.startswith("pb://") or furl.startswith("pbu://"):
44 return True
45 else:
46 return False
47
48 def find_furl(furl_or_file):
49 if isinstance(furl_or_file, str):
50 if is_valid(furl_or_file):
51 return furl_or_file
52 if os.path.isfile(furl_or_file):
53 furl = open(furl_or_file, 'r').read().strip()
54 if is_valid(furl):
55 return furl
56 raise ValueError("not a furl or a file containing a furl: %s" % furl_or_file)
57
58 # We do this so if a user doesn't have OpenSSL installed, it will try to use
59 # an UnauthenticatedTub. But, they will still run into problems if they
60 # try to use encrypted furls.
61 try:
62 import OpenSSL
63 except:
64 Tub = UnauthenticatedTub
65 have_crypto = False
66 else:
67 have_crypto = True
68
69
@@ -0,0 +1,171 b''
1 # encoding: utf-8
2
3 """Magic command interface for interactive parallel work."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
17
18 import new
19
20 from IPython.iplib import InteractiveShell
21 from IPython.Shell import MTInteractiveShell
22
23 from twisted.internet.defer import Deferred
24
25
26 #-------------------------------------------------------------------------------
27 # Definitions of magic functions for use with IPython
28 #-------------------------------------------------------------------------------
29
30 NO_ACTIVE_CONTROLLER = """
31 Error: No Controller is activated
32 Use activate() on a RemoteController object to activate it for magics.
33 """
34
35 def magic_result(self,parameter_s=''):
36 """Print the result of command i on all engines of the active controller.
37
38 To activate a controller in IPython, first create it and then call
39 the activate() method.
40
41 Then you can do the following:
42
43 >>> result # Print the latest result
44 Printing result...
45 [127.0.0.1:0] In [1]: b = 10
46 [127.0.0.1:1] In [1]: b = 10
47
48 >>> result 0 # Print result 0
49 In [14]: result 0
50 Printing result...
51 [127.0.0.1:0] In [0]: a = 5
52 [127.0.0.1:1] In [0]: a = 5
53 """
54 try:
55 activeController = __IPYTHON__.activeController
56 except AttributeError:
57 print NO_ACTIVE_CONTROLLER
58 else:
59 try:
60 index = int(parameter_s)
61 except:
62 index = None
63 result = activeController.get_result(index)
64 return result
65
66 def magic_px(self,parameter_s=''):
67 """Executes the given python command on the active IPython Controller.
68
69 To activate a Controller in IPython, first create it and then call
70 the activate() method.
71
72 Then you can do the following:
73
74 >>> %px a = 5 # Runs a = 5 on all nodes
75 """
76
77 try:
78 activeController = __IPYTHON__.activeController
79 except AttributeError:
80 print NO_ACTIVE_CONTROLLER
81 else:
82 print "Executing command on Controller"
83 result = activeController.execute(parameter_s)
84 return result
85
86 def pxrunsource(self, source, filename="<input>", symbol="single"):
87
88 try:
89 code = self.compile(source, filename, symbol)
90 except (OverflowError, SyntaxError, ValueError):
91 # Case 1
92 self.showsyntaxerror(filename)
93 return None
94
95 if code is None:
96 # Case 2
97 return True
98
99 # Case 3
100 # Because autopx is enabled, we now call executeAll or disable autopx if
101 # %autopx or autopx has been called
102 if '_ip.magic("%autopx' in source or '_ip.magic("autopx' in source:
103 _disable_autopx(self)
104 return False
105 else:
106 try:
107 result = self.activeController.execute(source)
108 except:
109 self.showtraceback()
110 else:
111 print result.__repr__()
112 return False
113
114 def magic_autopx(self, parameter_s=''):
115 """Toggles auto parallel mode for the active IPython Controller.
116
117 To activate a Controller in IPython, first create it and then call
118 the activate() method.
119
120 Then you can do the following:
121
122 >>> %autopx # Now all commands are executed in parallel
123 Auto Parallel Enabled
124 Type %autopx to disable
125 ...
126 >>> %autopx # Now all commands are locally executed
127 Auto Parallel Disabled
128 """
129
130 if hasattr(self, 'autopx'):
131 if self.autopx == True:
132 _disable_autopx(self)
133 else:
134 _enable_autopx(self)
135 else:
136 _enable_autopx(self)
137
138 def _enable_autopx(self):
139 """Enable %autopx mode by saving the original runsource and installing
140 pxrunsource.
141 """
142 try:
143 activeController = __IPYTHON__.activeController
144 except AttributeError:
145 print "No active RemoteController found, use RemoteController.activate()."
146 else:
147 self._original_runsource = self.runsource
148 self.runsource = new.instancemethod(pxrunsource, self, self.__class__)
149 self.autopx = True
150 print "Auto Parallel Enabled\nType %autopx to disable"
151
152 def _disable_autopx(self):
153 """Disable %autopx by restoring the original runsource."""
154 if hasattr(self, 'autopx'):
155 if self.autopx == True:
156 self.runsource = self._original_runsource
157 self.autopx = False
158 print "Auto Parallel Disabled"
159
160 # Add the new magic function to the class dict:
161
162 InteractiveShell.magic_result = magic_result
163 InteractiveShell.magic_px = magic_px
164 InteractiveShell.magic_autopx = magic_autopx
165
166 # And remove the global name to keep global namespace clean. Don't worry, the
167 # copy bound to IPython stays, we're just removing the global name.
168 del magic_result
169 del magic_px
170 del magic_autopx
171
@@ -0,0 +1,121 b''
1 # encoding: utf-8
2
3 """Classes used in scattering and gathering sequences.
4
5 Scattering consists of partitioning a sequence and sending the various
6 pieces to individual nodes in a cluster.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import types
23
24 from IPython.genutils import flatten as genutil_flatten
25
26 #-------------------------------------------------------------------------------
27 # Figure out which array packages are present and their array types
28 #-------------------------------------------------------------------------------
29
30 arrayModules = []
31 try:
32 import Numeric
33 except ImportError:
34 pass
35 else:
36 arrayModules.append({'module':Numeric, 'type':Numeric.arraytype})
37 try:
38 import numpy
39 except ImportError:
40 pass
41 else:
42 arrayModules.append({'module':numpy, 'type':numpy.ndarray})
43 try:
44 import numarray
45 except ImportError:
46 pass
47 else:
48 arrayModules.append({'module':numarray,
49 'type':numarray.numarraycore.NumArray})
50
51 class Map:
52 """A class for partitioning a sequence using a map."""
53
54 def getPartition(self, seq, p, q):
55 """Returns the pth partition of q partitions of seq."""
56
57 # Test for error conditions here
58 if p<0 or p>=q:
59 print "No partition exists."
60 return
61
62 remainder = len(seq)%q
63 basesize = len(seq)/q
64 hi = []
65 lo = []
66 for n in range(q):
67 if n < remainder:
68 lo.append(n * (basesize + 1))
69 hi.append(lo[-1] + basesize + 1)
70 else:
71 lo.append(n*basesize + remainder)
72 hi.append(lo[-1] + basesize)
73
74
75 result = seq[lo[p]:hi[p]]
76 return result
77
78 def joinPartitions(self, listOfPartitions):
79 return self.concatenate(listOfPartitions)
80
81 def concatenate(self, listOfPartitions):
82 testObject = listOfPartitions[0]
83 # First see if we have a known array type
84 for m in arrayModules:
85 #print m
86 if isinstance(testObject, m['type']):
87 return m['module'].concatenate(listOfPartitions)
88 # Next try for Python sequence types
89 if isinstance(testObject, (types.ListType, types.TupleType)):
90 return genutil_flatten(listOfPartitions)
91 # If we have scalars, just return listOfPartitions
92 return listOfPartitions
93
94 class RoundRobinMap(Map):
95 """Partitions a sequence in a roun robin fashion.
96
97 This currently does not work!
98 """
99
100 def getPartition(self, seq, p, q):
101 return seq[p:len(seq):q]
102 #result = []
103 #for i in range(p,len(seq),q):
104 # result.append(seq[i])
105 #return result
106
107 def joinPartitions(self, listOfPartitions):
108 #lengths = [len(x) for x in listOfPartitions]
109 #maxPartitionLength = len(listOfPartitions[0])
110 #numberOfPartitions = len(listOfPartitions)
111 #concat = self.concatenate(listOfPartitions)
112 #totalLength = len(concat)
113 #result = []
114 #for i in range(maxPartitionLength):
115 # result.append(concat[i:totalLength:maxPartitionLength])
116 return self.concatenate(listOfPartitions)
117
118 styles = {'basic':Map}
119
120
121
This diff has been collapsed as it changes many lines, (780 lines changed) Show them Hide them
@@ -0,0 +1,780 b''
1 # encoding: utf-8
2 # -*- test-case-name: IPython.kernel.test.test_multiengine -*-
3
4 """Adapt the IPython ControllerServer to IMultiEngine.
5
6 This module provides classes that adapt a ControllerService to the
7 IMultiEngine interface. This interface is a basic interactive interface
8 for working with a set of engines where it is desired to have explicit
9 access to each registered engine.
10
11 The classes here are exposed to the network in files like:
12
13 * multienginevanilla.py
14 * multienginepb.py
15 """
16
17 __docformat__ = "restructuredtext en"
18
19 #-------------------------------------------------------------------------------
20 # Copyright (C) 2008 The IPython Development Team
21 #
22 # Distributed under the terms of the BSD License. The full license is in
23 # the file COPYING, distributed as part of this software.
24 #-------------------------------------------------------------------------------
25
26 #-------------------------------------------------------------------------------
27 # Imports
28 #-------------------------------------------------------------------------------
29
30 from new import instancemethod
31 from types import FunctionType
32
33 from twisted.application import service
34 from twisted.internet import defer, reactor
35 from twisted.python import log, components, failure
36 from zope.interface import Interface, implements, Attribute
37
38 from IPython.tools import growl
39 from IPython.kernel.util import printer
40 from IPython.kernel.twistedutil import gatherBoth
41 from IPython.kernel import map as Map
42 from IPython.kernel import error
43 from IPython.kernel.pendingdeferred import PendingDeferredManager, two_phase
44 from IPython.kernel.controllerservice import \
45 ControllerAdapterBase, \
46 ControllerService, \
47 IControllerBase
48
49
50 #-------------------------------------------------------------------------------
51 # Interfaces for the MultiEngine representation of a controller
52 #-------------------------------------------------------------------------------
53
54 class IEngineMultiplexer(Interface):
55 """Interface to multiple engines implementing IEngineCore/Serialized/Queued.
56
57 This class simply acts as a multiplexer of methods that are in the
58 various IEngines* interfaces. Thus the methods here are jut like those
59 in the IEngine* interfaces, but with an extra first argument, targets.
60 The targets argument can have the following forms:
61
62 * targets = 10 # Engines are indexed by ints
63 * targets = [0,1,2,3] # A list of ints
64 * targets = 'all' # A string to indicate all targets
65
66 If targets is bad in any way, an InvalidEngineID will be raised. This
67 includes engines not being registered.
68
69 All IEngineMultiplexer multiplexer methods must return a Deferred to a list
70 with length equal to the number of targets. The elements of the list will
71 correspond to the return of the corresponding IEngine method.
72
73 Failures are aggressive, meaning that if an action fails for any target,
74 the overall action will fail immediately with that Failure.
75
76 :Parameters:
77 targets : int, list of ints, or 'all'
78 Engine ids the action will apply to.
79
80 :Returns: Deferred to a list of results for each engine.
81
82 :Exception:
83 InvalidEngineID
84 If the targets argument is bad or engines aren't registered.
85 NoEnginesRegistered
86 If there are no engines registered and targets='all'
87 """
88
89 #---------------------------------------------------------------------------
90 # Mutiplexed methods
91 #---------------------------------------------------------------------------
92
93 def execute(lines, targets='all'):
94 """Execute lines of Python code on targets.
95
96 See the class docstring for information about targets and possible
97 exceptions this method can raise.
98
99 :Parameters:
100 lines : str
101 String of python code to be executed on targets.
102 """
103
104 def push(namespace, targets='all'):
105 """Push dict namespace into the user's namespace on targets.
106
107 See the class docstring for information about targets and possible
108 exceptions this method can raise.
109
110 :Parameters:
111 namspace : dict
112 Dict of key value pairs to be put into the users namspace.
113 """
114
115 def pull(keys, targets='all'):
116 """Pull values out of the user's namespace on targets by keys.
117
118 See the class docstring for information about targets and possible
119 exceptions this method can raise.
120
121 :Parameters:
122 keys : tuple of strings
123 Sequence of keys to be pulled from user's namespace.
124 """
125
126 def push_function(namespace, targets='all'):
127 """"""
128
129 def pull_function(keys, targets='all'):
130 """"""
131
132 def get_result(i=None, targets='all'):
133 """Get the result for command i from targets.
134
135 See the class docstring for information about targets and possible
136 exceptions this method can raise.
137
138 :Parameters:
139 i : int or None
140 Command index or None to indicate most recent command.
141 """
142
143 def reset(targets='all'):
144 """Reset targets.
145
146 This clears the users namespace of the Engines, but won't cause
147 modules to be reloaded.
148 """
149
150 def keys(targets='all'):
151 """Get variable names defined in user's namespace on targets."""
152
153 def kill(controller=False, targets='all'):
154 """Kill the targets Engines and possibly the controller.
155
156 :Parameters:
157 controller : boolean
158 Should the controller be killed as well. If so all the
159 engines will be killed first no matter what targets is.
160 """
161
162 def push_serialized(namespace, targets='all'):
163 """Push a namespace of Serialized objects to targets.
164
165 :Parameters:
166 namespace : dict
167 A dict whose keys are the variable names and whose values
168 are serialized version of the objects.
169 """
170
171 def pull_serialized(keys, targets='all'):
172 """Pull Serialized objects by keys from targets.
173
174 :Parameters:
175 keys : tuple of strings
176 Sequence of variable names to pull as serialized objects.
177 """
178
179 def clear_queue(targets='all'):
180 """Clear the queue of pending command for targets."""
181
182 def queue_status(targets='all'):
183 """Get the status of the queue on the targets."""
184
185 def set_properties(properties, targets='all'):
186 """set properties by key and value"""
187
188 def get_properties(keys=None, targets='all'):
189 """get a list of properties by `keys`, if no keys specified, get all"""
190
191 def del_properties(keys, targets='all'):
192 """delete properties by `keys`"""
193
194 def has_properties(keys, targets='all'):
195 """get a list of bool values for whether `properties` has `keys`"""
196
197 def clear_properties(targets='all'):
198 """clear the properties dict"""
199
200
201 class IMultiEngine(IEngineMultiplexer):
202 """A controller that exposes an explicit interface to all of its engines.
203
204 This is the primary inteface for interactive usage.
205 """
206
207 def get_ids():
208 """Return list of currently registered ids.
209
210 :Returns: A Deferred to a list of registered engine ids.
211 """
212
213
214
215 #-------------------------------------------------------------------------------
216 # Implementation of the core MultiEngine classes
217 #-------------------------------------------------------------------------------
218
219 class MultiEngine(ControllerAdapterBase):
220 """The representation of a ControllerService as a IMultiEngine.
221
222 Although it is not implemented currently, this class would be where a
223 client/notification API is implemented. It could inherit from something
224 like results.NotifierParent and then use the notify method to send
225 notifications.
226 """
227
228 implements(IMultiEngine)
229
230 def __init(self, controller):
231 ControllerAdapterBase.__init__(self, controller)
232
233 #---------------------------------------------------------------------------
234 # Helper methods
235 #---------------------------------------------------------------------------
236
237 def engineList(self, targets):
238 """Parse the targets argument into a list of valid engine objects.
239
240 :Parameters:
241 targets : int, list of ints or 'all'
242 The targets argument to be parsed.
243
244 :Returns: List of engine objects.
245
246 :Exception:
247 InvalidEngineID
248 If targets is not valid or if an engine is not registered.
249 """
250 if isinstance(targets, int):
251 if targets not in self.engines.keys():
252 log.msg("Engine with id %i is not registered" % targets)
253 raise error.InvalidEngineID("Engine with id %i is not registered" % targets)
254 else:
255 return [self.engines[targets]]
256 elif isinstance(targets, (list, tuple)):
257 for id in targets:
258 if id not in self.engines.keys():
259 log.msg("Engine with id %r is not registered" % id)
260 raise error.InvalidEngineID("Engine with id %r is not registered" % id)
261 return map(self.engines.get, targets)
262 elif targets == 'all':
263 eList = self.engines.values()
264 if len(eList) == 0:
265 msg = """There are no engines registered.
266 Check the logs in ~/.ipython/log if you think there should have been."""
267 raise error.NoEnginesRegistered(msg)
268 else:
269 return eList
270 else:
271 raise error.InvalidEngineID("targets argument is not an int, list of ints or 'all': %r"%targets)
272
273 def _performOnEngines(self, methodName, *args, **kwargs):
274 """Calls a method on engines and returns deferred to list of results.
275
276 :Parameters:
277 methodName : str
278 Name of the method to be called.
279 targets : int, list of ints, 'all'
280 The targets argument to be parsed into a list of engine objects.
281 args
282 The positional keyword arguments to be passed to the engines.
283 kwargs
284 The keyword arguments passed to the method
285
286 :Returns: List of deferreds to the results on each engine
287
288 :Exception:
289 InvalidEngineID
290 If the targets argument is bad in any way.
291 AttributeError
292 If the method doesn't exist on one of the engines.
293 """
294 targets = kwargs.pop('targets')
295 log.msg("Performing %s on %r" % (methodName, targets))
296 # log.msg("Performing %s(%r, %r) on %r" % (methodName, args, kwargs, targets))
297 # This will and should raise if targets is not valid!
298 engines = self.engineList(targets)
299 dList = []
300 for e in engines:
301 meth = getattr(e, methodName, None)
302 if meth is not None:
303 dList.append(meth(*args, **kwargs))
304 else:
305 raise AttributeError("Engine %i does not have method %s" % (e.id, methodName))
306 return dList
307
308 def _performOnEnginesAndGatherBoth(self, methodName, *args, **kwargs):
309 """Called _performOnEngines and wraps result/exception into deferred."""
310 try:
311 dList = self._performOnEngines(methodName, *args, **kwargs)
312 except (error.InvalidEngineID, AttributeError, KeyError, error.NoEnginesRegistered):
313 return defer.fail(failure.Failure())
314 else:
315 # Having fireOnOneErrback is causing problems with the determinacy
316 # of the system. Basically, once a single engine has errbacked, this
317 # method returns. In some cases, this will cause client to submit
318 # another command. Because the previous command is still running
319 # on some engines, this command will be queued. When those commands
320 # then errback, the second command will raise QueueCleared. Ahhh!
321 d = gatherBoth(dList,
322 fireOnOneErrback=0,
323 consumeErrors=1,
324 logErrors=0)
325 d.addCallback(error.collect_exceptions, methodName)
326 return d
327
328 #---------------------------------------------------------------------------
329 # General IMultiEngine methods
330 #---------------------------------------------------------------------------
331
332 def get_ids(self):
333 return defer.succeed(self.engines.keys())
334
335 #---------------------------------------------------------------------------
336 # IEngineMultiplexer methods
337 #---------------------------------------------------------------------------
338
339 def execute(self, lines, targets='all'):
340 return self._performOnEnginesAndGatherBoth('execute', lines, targets=targets)
341
342 def push(self, ns, targets='all'):
343 return self._performOnEnginesAndGatherBoth('push', ns, targets=targets)
344
345 def pull(self, keys, targets='all'):
346 return self._performOnEnginesAndGatherBoth('pull', keys, targets=targets)
347
348 def push_function(self, ns, targets='all'):
349 return self._performOnEnginesAndGatherBoth('push_function', ns, targets=targets)
350
351 def pull_function(self, keys, targets='all'):
352 return self._performOnEnginesAndGatherBoth('pull_function', keys, targets=targets)
353
354 def get_result(self, i=None, targets='all'):
355 return self._performOnEnginesAndGatherBoth('get_result', i, targets=targets)
356
357 def reset(self, targets='all'):
358 return self._performOnEnginesAndGatherBoth('reset', targets=targets)
359
360 def keys(self, targets='all'):
361 return self._performOnEnginesAndGatherBoth('keys', targets=targets)
362
363 def kill(self, controller=False, targets='all'):
364 if controller:
365 targets = 'all'
366 d = self._performOnEnginesAndGatherBoth('kill', targets=targets)
367 if controller:
368 log.msg("Killing controller")
369 d.addCallback(lambda _: reactor.callLater(2.0, reactor.stop))
370 # Consume any weird stuff coming back
371 d.addBoth(lambda _: None)
372 return d
373
374 def push_serialized(self, namespace, targets='all'):
375 for k, v in namespace.iteritems():
376 log.msg("Pushed object %s is %f MB" % (k, v.getDataSize()))
377 d = self._performOnEnginesAndGatherBoth('push_serialized', namespace, targets=targets)
378 return d
379
380 def pull_serialized(self, keys, targets='all'):
381 try:
382 dList = self._performOnEngines('pull_serialized', keys, targets=targets)
383 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
384 return defer.fail(failure.Failure())
385 else:
386 for d in dList:
387 d.addCallback(self._logSizes)
388 d = gatherBoth(dList,
389 fireOnOneErrback=0,
390 consumeErrors=1,
391 logErrors=0)
392 d.addCallback(error.collect_exceptions, 'pull_serialized')
393 return d
394
395 def _logSizes(self, listOfSerialized):
396 if isinstance(listOfSerialized, (list, tuple)):
397 for s in listOfSerialized:
398 log.msg("Pulled object is %f MB" % s.getDataSize())
399 else:
400 log.msg("Pulled object is %f MB" % listOfSerialized.getDataSize())
401 return listOfSerialized
402
403 def clear_queue(self, targets='all'):
404 return self._performOnEnginesAndGatherBoth('clear_queue', targets=targets)
405
406 def queue_status(self, targets='all'):
407 log.msg("Getting queue status on %r" % targets)
408 try:
409 engines = self.engineList(targets)
410 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
411 return defer.fail(failure.Failure())
412 else:
413 dList = []
414 for e in engines:
415 dList.append(e.queue_status().addCallback(lambda s:(e.id, s)))
416 d = gatherBoth(dList,
417 fireOnOneErrback=0,
418 consumeErrors=1,
419 logErrors=0)
420 d.addCallback(error.collect_exceptions, 'queue_status')
421 return d
422
423 def get_properties(self, keys=None, targets='all'):
424 log.msg("Getting properties on %r" % targets)
425 try:
426 engines = self.engineList(targets)
427 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
428 return defer.fail(failure.Failure())
429 else:
430 dList = [e.get_properties(keys) for e in engines]
431 d = gatherBoth(dList,
432 fireOnOneErrback=0,
433 consumeErrors=1,
434 logErrors=0)
435 d.addCallback(error.collect_exceptions, 'get_properties')
436 return d
437
438 def set_properties(self, properties, targets='all'):
439 log.msg("Setting properties on %r" % targets)
440 try:
441 engines = self.engineList(targets)
442 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
443 return defer.fail(failure.Failure())
444 else:
445 dList = [e.set_properties(properties) for e in engines]
446 d = gatherBoth(dList,
447 fireOnOneErrback=0,
448 consumeErrors=1,
449 logErrors=0)
450 d.addCallback(error.collect_exceptions, 'set_properties')
451 return d
452
453 def has_properties(self, keys, targets='all'):
454 log.msg("Checking properties on %r" % targets)
455 try:
456 engines = self.engineList(targets)
457 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
458 return defer.fail(failure.Failure())
459 else:
460 dList = [e.has_properties(keys) for e in engines]
461 d = gatherBoth(dList,
462 fireOnOneErrback=0,
463 consumeErrors=1,
464 logErrors=0)
465 d.addCallback(error.collect_exceptions, 'has_properties')
466 return d
467
468 def del_properties(self, keys, targets='all'):
469 log.msg("Deleting properties on %r" % targets)
470 try:
471 engines = self.engineList(targets)
472 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
473 return defer.fail(failure.Failure())
474 else:
475 dList = [e.del_properties(keys) for e in engines]
476 d = gatherBoth(dList,
477 fireOnOneErrback=0,
478 consumeErrors=1,
479 logErrors=0)
480 d.addCallback(error.collect_exceptions, 'del_properties')
481 return d
482
483 def clear_properties(self, targets='all'):
484 log.msg("Clearing properties on %r" % targets)
485 try:
486 engines = self.engineList(targets)
487 except (error.InvalidEngineID, AttributeError, error.NoEnginesRegistered):
488 return defer.fail(failure.Failure())
489 else:
490 dList = [e.clear_properties() for e in engines]
491 d = gatherBoth(dList,
492 fireOnOneErrback=0,
493 consumeErrors=1,
494 logErrors=0)
495 d.addCallback(error.collect_exceptions, 'clear_properties')
496 return d
497
498
499 components.registerAdapter(MultiEngine,
500 IControllerBase,
501 IMultiEngine)
502
503
504 #-------------------------------------------------------------------------------
505 # Interfaces for the Synchronous MultiEngine
506 #-------------------------------------------------------------------------------
507
508 class ISynchronousEngineMultiplexer(Interface):
509 pass
510
511
512 class ISynchronousMultiEngine(ISynchronousEngineMultiplexer):
513 """Synchronous, two-phase version of IMultiEngine.
514
515 Methods in this interface are identical to those of IMultiEngine, but they
516 take one additional argument:
517
518 execute(lines, targets='all') -> execute(lines, targets='all, block=True)
519
520 :Parameters:
521 block : boolean
522 Should the method return a deferred to a deferredID or the
523 actual result. If block=False a deferred to a deferredID is
524 returned and the user must call `get_pending_deferred` at a later
525 point. If block=True, a deferred to the actual result comes back.
526 """
527 def get_pending_deferred(deferredID, block=True):
528 """"""
529
530 def clear_pending_deferreds():
531 """"""
532
533
534 #-------------------------------------------------------------------------------
535 # Implementation of the Synchronous MultiEngine
536 #-------------------------------------------------------------------------------
537
538 class SynchronousMultiEngine(PendingDeferredManager):
539 """Adapt an `IMultiEngine` -> `ISynchronousMultiEngine`
540
541 Warning, this class uses a decorator that currently uses **kwargs.
542 Because of this block must be passed as a kwarg, not positionally.
543 """
544
545 implements(ISynchronousMultiEngine)
546
547 def __init__(self, multiengine):
548 self.multiengine = multiengine
549 PendingDeferredManager.__init__(self)
550
551 #---------------------------------------------------------------------------
552 # Decorated pending deferred methods
553 #---------------------------------------------------------------------------
554
555 @two_phase
556 def execute(self, lines, targets='all'):
557 d = self.multiengine.execute(lines, targets)
558 return d
559
560 @two_phase
561 def push(self, namespace, targets='all'):
562 return self.multiengine.push(namespace, targets)
563
564 @two_phase
565 def pull(self, keys, targets='all'):
566 d = self.multiengine.pull(keys, targets)
567 return d
568
569 @two_phase
570 def push_function(self, namespace, targets='all'):
571 return self.multiengine.push_function(namespace, targets)
572
573 @two_phase
574 def pull_function(self, keys, targets='all'):
575 d = self.multiengine.pull_function(keys, targets)
576 return d
577
578 @two_phase
579 def get_result(self, i=None, targets='all'):
580 return self.multiengine.get_result(i, targets='all')
581
582 @two_phase
583 def reset(self, targets='all'):
584 return self.multiengine.reset(targets)
585
586 @two_phase
587 def keys(self, targets='all'):
588 return self.multiengine.keys(targets)
589
590 @two_phase
591 def kill(self, controller=False, targets='all'):
592 return self.multiengine.kill(controller, targets)
593
594 @two_phase
595 def push_serialized(self, namespace, targets='all'):
596 return self.multiengine.push_serialized(namespace, targets)
597
598 @two_phase
599 def pull_serialized(self, keys, targets='all'):
600 return self.multiengine.pull_serialized(keys, targets)
601
602 @two_phase
603 def clear_queue(self, targets='all'):
604 return self.multiengine.clear_queue(targets)
605
606 @two_phase
607 def queue_status(self, targets='all'):
608 return self.multiengine.queue_status(targets)
609
610 @two_phase
611 def set_properties(self, properties, targets='all'):
612 return self.multiengine.set_properties(properties, targets)
613
614 @two_phase
615 def get_properties(self, keys=None, targets='all'):
616 return self.multiengine.get_properties(keys, targets)
617
618 @two_phase
619 def has_properties(self, keys, targets='all'):
620 return self.multiengine.has_properties(keys, targets)
621
622 @two_phase
623 def del_properties(self, keys, targets='all'):
624 return self.multiengine.del_properties(keys, targets)
625
626 @two_phase
627 def clear_properties(self, targets='all'):
628 return self.multiengine.clear_properties(targets)
629
630 #---------------------------------------------------------------------------
631 # IMultiEngine methods
632 #---------------------------------------------------------------------------
633
634 def get_ids(self):
635 """Return a list of registered engine ids.
636
637 Never use the two phase block/non-block stuff for this.
638 """
639 return self.multiengine.get_ids()
640
641
642 components.registerAdapter(SynchronousMultiEngine, IMultiEngine, ISynchronousMultiEngine)
643
644
645 #-------------------------------------------------------------------------------
646 # Various high-level interfaces that can be used as MultiEngine mix-ins
647 #-------------------------------------------------------------------------------
648
649 #-------------------------------------------------------------------------------
650 # IMultiEngineCoordinator
651 #-------------------------------------------------------------------------------
652
653 class IMultiEngineCoordinator(Interface):
654 """Methods that work on multiple engines explicitly."""
655
656 def scatter(key, seq, style='basic', flatten=False, targets='all'):
657 """Partition and distribute a sequence to targets.
658
659 :Parameters:
660 key : str
661 The variable name to call the scattered sequence.
662 seq : list, tuple, array
663 The sequence to scatter. The type should be preserved.
664 style : string
665 A specification of how the sequence is partitioned. Currently
666 only 'basic' is implemented.
667 flatten : boolean
668 Should single element sequences be converted to scalars.
669 """
670
671 def gather(key, style='basic', targets='all'):
672 """Gather object key from targets.
673
674 :Parameters:
675 key : string
676 The name of a sequence on the targets to gather.
677 style : string
678 A specification of how the sequence is partitioned. Currently
679 only 'basic' is implemented.
680 """
681
682 def map(func, seq, style='basic', targets='all'):
683 """A parallelized version of Python's builtin map.
684
685 This function implements the following pattern:
686
687 1. The sequence seq is scattered to the given targets.
688 2. map(functionSource, seq) is called on each engine.
689 3. The resulting sequences are gathered back to the local machine.
690
691 :Parameters:
692 targets : int, list or 'all'
693 The engine ids the action will apply to. Call `get_ids` to see
694 a list of currently available engines.
695 func : str, function
696 An actual function object or a Python string that names a
697 callable defined on the engines.
698 seq : list, tuple or numpy array
699 The local sequence to be scattered.
700 style : str
701 Only 'basic' is supported for now.
702
703 :Returns: A list of len(seq) with functionSource called on each element
704 of seq.
705
706 Example
707 =======
708
709 >>> rc.mapAll('lambda x: x*x', range(10000))
710 [0,2,4,9,25,36,...]
711 """
712
713
714 class ISynchronousMultiEngineCoordinator(IMultiEngineCoordinator):
715 """Methods that work on multiple engines explicitly."""
716 pass
717
718
719 #-------------------------------------------------------------------------------
720 # IMultiEngineExtras
721 #-------------------------------------------------------------------------------
722
723 class IMultiEngineExtras(Interface):
724
725 def zip_pull(targets, *keys):
726 """Pull, but return results in a different format from `pull`.
727
728 This method basically returns zip(pull(targets, *keys)), with a few
729 edge cases handled differently. Users of chainsaw will find this format
730 familiar.
731
732 :Parameters:
733 targets : int, list or 'all'
734 The engine ids the action will apply to. Call `get_ids` to see
735 a list of currently available engines.
736 keys: list or tuple of str
737 A list of variable names as string of the Python objects to be pulled
738 back to the client.
739
740 :Returns: A list of pulled Python objects for each target.
741 """
742
743 def run(targets, fname):
744 """Run a .py file on targets.
745
746 :Parameters:
747 targets : int, list or 'all'
748 The engine ids the action will apply to. Call `get_ids` to see
749 a list of currently available engines.
750 fname : str
751 The filename of a .py file on the local system to be sent to and run
752 on the engines.
753 block : boolean
754 Should I block or not. If block=True, wait for the action to
755 complete and return the result. If block=False, return a
756 `PendingResult` object that can be used to later get the
757 result. If block is not specified, the block attribute
758 will be used instead.
759 """
760
761
762 class ISynchronousMultiEngineExtras(IMultiEngineExtras):
763 pass
764
765
766 #-------------------------------------------------------------------------------
767 # The full MultiEngine interface
768 #-------------------------------------------------------------------------------
769
770 class IFullMultiEngine(IMultiEngine,
771 IMultiEngineCoordinator,
772 IMultiEngineExtras):
773 pass
774
775
776 class IFullSynchronousMultiEngine(ISynchronousMultiEngine,
777 ISynchronousMultiEngineCoordinator,
778 ISynchronousMultiEngineExtras):
779 pass
780
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -32,7 +32,7 b' else:'
32 32
33 33 version = '0.8.4'
34 34
35 description = "An enhanced interactive Python shell."
35 description = "Tools for interactive development in Python."
36 36
37 37 long_description = \
38 38 """
@@ -77,13 +77,19 b" license = 'BSD'"
77 77 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
78 78 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
79 79 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
80 'Ville' : ('Ville Vainio','vivainio@gmail.com')
80 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
81 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
82 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
81 83 }
82 84
85 author = 'The IPython Development Team'
86
87 author_email = 'ipython-dev@scipy.org'
88
83 89 url = 'http://ipython.scipy.org'
84 90
85 91 download_url = 'http://ipython.scipy.org/dist'
86 92
87 93 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
88 94
89 keywords = ['Interactive','Interpreter','Shell']
95 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from eggsetup.py to setupegg.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from exesetup.py to setupexe.py
General Comments 0
You need to be logged in to leave comments. Login now