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 | ||||
|
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 | ||||
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | version = '0.8.4' |
|
33 | version = '0.8.4' | |
34 |
|
34 | |||
35 |
description = " |
|
35 | description = "Tools for interactive development in Python." | |
36 |
|
36 | |||
37 | long_description = \ |
|
37 | long_description = \ | |
38 | """ |
|
38 | """ | |
@@ -77,13 +77,19 b" license = 'BSD'" | |||||
77 | authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'), |
|
77 | authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'), | |
78 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), |
|
78 | 'Janko' : ('Janko Hauser','jhauser@zscout.de'), | |
79 | 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'), |
|
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 | url = 'http://ipython.scipy.org' |
|
89 | url = 'http://ipython.scipy.org' | |
84 |
|
90 | |||
85 | download_url = 'http://ipython.scipy.org/dist' |
|
91 | download_url = 'http://ipython.scipy.org/dist' | |
86 |
|
92 | |||
87 | platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME'] |
|
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 |
|
NO CONTENT: modified file | ||
The requested commit or file is too big and content was truncated. Show full diff |
1 | NO CONTENT: modified file |
|
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 |
|
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 |
|
NO CONTENT: file renamed from exesetup.py to setupexe.py |
General Comments 0
You need to be logged in to leave comments.
Login now