##// END OF EJS Templates
Statically type OInfo. (#13973)...
Matthias Bussonnier -
r28166:29b451fc merge
parent child Browse files
Show More

The requested changes are 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,1080 +1,1093 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for inspecting Python objects.
2 """Tools for inspecting Python objects.
3
3
4 Uses syntax highlighting for presenting the various information elements.
4 Uses syntax highlighting for presenting the various information elements.
5
5
6 Similar in spirit to the inspect module, but all calls take a name argument to
6 Similar in spirit to the inspect module, but all calls take a name argument to
7 reference the name under which an object is being read.
7 reference the name under which an object is being read.
8 """
8 """
9
9
10 # Copyright (c) IPython Development Team.
10 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12
12
13 __all__ = ['Inspector','InspectColors']
13 __all__ = ['Inspector','InspectColors']
14
14
15 # stdlib modules
15 # stdlib modules
16 import ast
16 import ast
17 import inspect
17 import inspect
18 from inspect import signature
18 from inspect import signature
19 import html
19 import html
20 import linecache
20 import linecache
21 import warnings
21 import warnings
22 import os
22 import os
23 from textwrap import dedent
23 from textwrap import dedent
24 import types
24 import types
25 import io as stdlib_io
25 import io as stdlib_io
26
26
27 from typing import Union
27 from typing import Union
28
28
29 # IPython's own
29 # IPython's own
30 from IPython.core import page
30 from IPython.core import page
31 from IPython.lib.pretty import pretty
31 from IPython.lib.pretty import pretty
32 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.testing.skipdoctest import skip_doctest
33 from IPython.utils import PyColorize
33 from IPython.utils import PyColorize
34 from IPython.utils import openpy
34 from IPython.utils import openpy
35 from IPython.utils.dir2 import safe_hasattr
35 from IPython.utils.dir2 import safe_hasattr
36 from IPython.utils.path import compress_user
36 from IPython.utils.path import compress_user
37 from IPython.utils.text import indent
37 from IPython.utils.text import indent
38 from IPython.utils.wildcard import list_namespace
38 from IPython.utils.wildcard import list_namespace
39 from IPython.utils.wildcard import typestr2type
39 from IPython.utils.wildcard import typestr2type
40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
41 from IPython.utils.py3compat import cast_unicode
41 from IPython.utils.py3compat import cast_unicode
42 from IPython.utils.colorable import Colorable
42 from IPython.utils.colorable import Colorable
43 from IPython.utils.decorators import undoc
43 from IPython.utils.decorators import undoc
44
44
45 from pygments import highlight
45 from pygments import highlight
46 from pygments.lexers import PythonLexer
46 from pygments.lexers import PythonLexer
47 from pygments.formatters import HtmlFormatter
47 from pygments.formatters import HtmlFormatter
48
48
49 from typing import Any
50 from dataclasses import dataclass
51
52
53 @dataclass
54 class OInfo:
55 ismagic: bool
56 isalias: bool
57 found: bool
58 namespace: str
59 parent: Any
60 obj: Any
61
49 def pylight(code):
62 def pylight(code):
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51
64
52 # builtin docstrings to ignore
65 # builtin docstrings to ignore
53 _func_call_docstring = types.FunctionType.__call__.__doc__
66 _func_call_docstring = types.FunctionType.__call__.__doc__
54 _object_init_docstring = object.__init__.__doc__
67 _object_init_docstring = object.__init__.__doc__
55 _builtin_type_docstrings = {
68 _builtin_type_docstrings = {
56 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
69 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
57 types.FunctionType, property)
70 types.FunctionType, property)
58 }
71 }
59
72
60 _builtin_func_type = type(all)
73 _builtin_func_type = type(all)
61 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
74 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
62 #****************************************************************************
75 #****************************************************************************
63 # Builtin color schemes
76 # Builtin color schemes
64
77
65 Colors = TermColors # just a shorthand
78 Colors = TermColors # just a shorthand
66
79
67 InspectColors = PyColorize.ANSICodeColors
80 InspectColors = PyColorize.ANSICodeColors
68
81
69 #****************************************************************************
82 #****************************************************************************
70 # Auxiliary functions and objects
83 # Auxiliary functions and objects
71
84
72 # See the messaging spec for the definition of all these fields. This list
85 # See the messaging spec for the definition of all these fields. This list
73 # effectively defines the order of display
86 # effectively defines the order of display
74 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
87 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
75 'length', 'file', 'definition', 'docstring', 'source',
88 'length', 'file', 'definition', 'docstring', 'source',
76 'init_definition', 'class_docstring', 'init_docstring',
89 'init_definition', 'class_docstring', 'init_docstring',
77 'call_def', 'call_docstring',
90 'call_def', 'call_docstring',
78 # These won't be printed but will be used to determine how to
91 # These won't be printed but will be used to determine how to
79 # format the object
92 # format the object
80 'ismagic', 'isalias', 'isclass', 'found', 'name'
93 'ismagic', 'isalias', 'isclass', 'found', 'name'
81 ]
94 ]
82
95
83
96
84 def object_info(**kw):
97 def object_info(**kw):
85 """Make an object info dict with all fields present."""
98 """Make an object info dict with all fields present."""
86 infodict = {k:None for k in info_fields}
99 infodict = {k:None for k in info_fields}
87 infodict.update(kw)
100 infodict.update(kw)
88 return infodict
101 return infodict
89
102
90
103
91 def get_encoding(obj):
104 def get_encoding(obj):
92 """Get encoding for python source file defining obj
105 """Get encoding for python source file defining obj
93
106
94 Returns None if obj is not defined in a sourcefile.
107 Returns None if obj is not defined in a sourcefile.
95 """
108 """
96 ofile = find_file(obj)
109 ofile = find_file(obj)
97 # run contents of file through pager starting at line where the object
110 # run contents of file through pager starting at line where the object
98 # is defined, as long as the file isn't binary and is actually on the
111 # is defined, as long as the file isn't binary and is actually on the
99 # filesystem.
112 # filesystem.
100 if ofile is None:
113 if ofile is None:
101 return None
114 return None
102 elif ofile.endswith(('.so', '.dll', '.pyd')):
115 elif ofile.endswith(('.so', '.dll', '.pyd')):
103 return None
116 return None
104 elif not os.path.isfile(ofile):
117 elif not os.path.isfile(ofile):
105 return None
118 return None
106 else:
119 else:
107 # Print only text files, not extension binaries. Note that
120 # Print only text files, not extension binaries. Note that
108 # getsourcelines returns lineno with 1-offset and page() uses
121 # getsourcelines returns lineno with 1-offset and page() uses
109 # 0-offset, so we must adjust.
122 # 0-offset, so we must adjust.
110 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
123 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
111 encoding, lines = openpy.detect_encoding(buffer.readline)
124 encoding, lines = openpy.detect_encoding(buffer.readline)
112 return encoding
125 return encoding
113
126
114 def getdoc(obj) -> Union[str,None]:
127 def getdoc(obj) -> Union[str,None]:
115 """Stable wrapper around inspect.getdoc.
128 """Stable wrapper around inspect.getdoc.
116
129
117 This can't crash because of attribute problems.
130 This can't crash because of attribute problems.
118
131
119 It also attempts to call a getdoc() method on the given object. This
132 It also attempts to call a getdoc() method on the given object. This
120 allows objects which provide their docstrings via non-standard mechanisms
133 allows objects which provide their docstrings via non-standard mechanisms
121 (like Pyro proxies) to still be inspected by ipython's ? system.
134 (like Pyro proxies) to still be inspected by ipython's ? system.
122 """
135 """
123 # Allow objects to offer customized documentation via a getdoc method:
136 # Allow objects to offer customized documentation via a getdoc method:
124 try:
137 try:
125 ds = obj.getdoc()
138 ds = obj.getdoc()
126 except Exception:
139 except Exception:
127 pass
140 pass
128 else:
141 else:
129 if isinstance(ds, str):
142 if isinstance(ds, str):
130 return inspect.cleandoc(ds)
143 return inspect.cleandoc(ds)
131 docstr = inspect.getdoc(obj)
144 docstr = inspect.getdoc(obj)
132 return docstr
145 return docstr
133
146
134
147
135 def getsource(obj, oname='') -> Union[str,None]:
148 def getsource(obj, oname='') -> Union[str,None]:
136 """Wrapper around inspect.getsource.
149 """Wrapper around inspect.getsource.
137
150
138 This can be modified by other projects to provide customized source
151 This can be modified by other projects to provide customized source
139 extraction.
152 extraction.
140
153
141 Parameters
154 Parameters
142 ----------
155 ----------
143 obj : object
156 obj : object
144 an object whose source code we will attempt to extract
157 an object whose source code we will attempt to extract
145 oname : str
158 oname : str
146 (optional) a name under which the object is known
159 (optional) a name under which the object is known
147
160
148 Returns
161 Returns
149 -------
162 -------
150 src : unicode or None
163 src : unicode or None
151
164
152 """
165 """
153
166
154 if isinstance(obj, property):
167 if isinstance(obj, property):
155 sources = []
168 sources = []
156 for attrname in ['fget', 'fset', 'fdel']:
169 for attrname in ['fget', 'fset', 'fdel']:
157 fn = getattr(obj, attrname)
170 fn = getattr(obj, attrname)
158 if fn is not None:
171 if fn is not None:
159 encoding = get_encoding(fn)
172 encoding = get_encoding(fn)
160 oname_prefix = ('%s.' % oname) if oname else ''
173 oname_prefix = ('%s.' % oname) if oname else ''
161 sources.append(''.join(('# ', oname_prefix, attrname)))
174 sources.append(''.join(('# ', oname_prefix, attrname)))
162 if inspect.isfunction(fn):
175 if inspect.isfunction(fn):
163 sources.append(dedent(getsource(fn)))
176 sources.append(dedent(getsource(fn)))
164 else:
177 else:
165 # Default str/repr only prints function name,
178 # Default str/repr only prints function name,
166 # pretty.pretty prints module name too.
179 # pretty.pretty prints module name too.
167 sources.append(
180 sources.append(
168 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
181 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
169 )
182 )
170 if sources:
183 if sources:
171 return '\n'.join(sources)
184 return '\n'.join(sources)
172 else:
185 else:
173 return None
186 return None
174
187
175 else:
188 else:
176 # Get source for non-property objects.
189 # Get source for non-property objects.
177
190
178 obj = _get_wrapped(obj)
191 obj = _get_wrapped(obj)
179
192
180 try:
193 try:
181 src = inspect.getsource(obj)
194 src = inspect.getsource(obj)
182 except TypeError:
195 except TypeError:
183 # The object itself provided no meaningful source, try looking for
196 # The object itself provided no meaningful source, try looking for
184 # its class definition instead.
197 # its class definition instead.
185 try:
198 try:
186 src = inspect.getsource(obj.__class__)
199 src = inspect.getsource(obj.__class__)
187 except (OSError, TypeError):
200 except (OSError, TypeError):
188 return None
201 return None
189 except OSError:
202 except OSError:
190 return None
203 return None
191
204
192 return src
205 return src
193
206
194
207
195 def is_simple_callable(obj):
208 def is_simple_callable(obj):
196 """True if obj is a function ()"""
209 """True if obj is a function ()"""
197 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
210 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
198 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
211 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
199
212
200 @undoc
213 @undoc
201 def getargspec(obj):
214 def getargspec(obj):
202 """Wrapper around :func:`inspect.getfullargspec`
215 """Wrapper around :func:`inspect.getfullargspec`
203
216
204 In addition to functions and methods, this can also handle objects with a
217 In addition to functions and methods, this can also handle objects with a
205 ``__call__`` attribute.
218 ``__call__`` attribute.
206
219
207 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
220 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
208 """
221 """
209
222
210 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
223 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
211 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
224 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
212
225
213 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
226 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
214 obj = obj.__call__
227 obj = obj.__call__
215
228
216 return inspect.getfullargspec(obj)
229 return inspect.getfullargspec(obj)
217
230
218 @undoc
231 @undoc
219 def format_argspec(argspec):
232 def format_argspec(argspec):
220 """Format argspect, convenience wrapper around inspect's.
233 """Format argspect, convenience wrapper around inspect's.
221
234
222 This takes a dict instead of ordered arguments and calls
235 This takes a dict instead of ordered arguments and calls
223 inspect.format_argspec with the arguments in the necessary order.
236 inspect.format_argspec with the arguments in the necessary order.
224
237
225 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
238 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
226 """
239 """
227
240
228 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
241 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
229 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
242 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
230
243
231
244
232 return inspect.formatargspec(argspec['args'], argspec['varargs'],
245 return inspect.formatargspec(argspec['args'], argspec['varargs'],
233 argspec['varkw'], argspec['defaults'])
246 argspec['varkw'], argspec['defaults'])
234
247
235 @undoc
248 @undoc
236 def call_tip(oinfo, format_call=True):
249 def call_tip(oinfo, format_call=True):
237 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
250 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
238 warnings.warn(
251 warnings.warn(
239 "`call_tip` function is deprecated as of IPython 6.0"
252 "`call_tip` function is deprecated as of IPython 6.0"
240 "and will be removed in future versions.",
253 "and will be removed in future versions.",
241 DeprecationWarning,
254 DeprecationWarning,
242 stacklevel=2,
255 stacklevel=2,
243 )
256 )
244 # Get call definition
257 # Get call definition
245 argspec = oinfo.get('argspec')
258 argspec = oinfo.get('argspec')
246 if argspec is None:
259 if argspec is None:
247 call_line = None
260 call_line = None
248 else:
261 else:
249 # Callable objects will have 'self' as their first argument, prune
262 # Callable objects will have 'self' as their first argument, prune
250 # it out if it's there for clarity (since users do *not* pass an
263 # it out if it's there for clarity (since users do *not* pass an
251 # extra first argument explicitly).
264 # extra first argument explicitly).
252 try:
265 try:
253 has_self = argspec['args'][0] == 'self'
266 has_self = argspec['args'][0] == 'self'
254 except (KeyError, IndexError):
267 except (KeyError, IndexError):
255 pass
268 pass
256 else:
269 else:
257 if has_self:
270 if has_self:
258 argspec['args'] = argspec['args'][1:]
271 argspec['args'] = argspec['args'][1:]
259
272
260 call_line = oinfo['name']+format_argspec(argspec)
273 call_line = oinfo['name']+format_argspec(argspec)
261
274
262 # Now get docstring.
275 # Now get docstring.
263 # The priority is: call docstring, constructor docstring, main one.
276 # The priority is: call docstring, constructor docstring, main one.
264 doc = oinfo.get('call_docstring')
277 doc = oinfo.get('call_docstring')
265 if doc is None:
278 if doc is None:
266 doc = oinfo.get('init_docstring')
279 doc = oinfo.get('init_docstring')
267 if doc is None:
280 if doc is None:
268 doc = oinfo.get('docstring','')
281 doc = oinfo.get('docstring','')
269
282
270 return call_line, doc
283 return call_line, doc
271
284
272
285
273 def _get_wrapped(obj):
286 def _get_wrapped(obj):
274 """Get the original object if wrapped in one or more @decorators
287 """Get the original object if wrapped in one or more @decorators
275
288
276 Some objects automatically construct similar objects on any unrecognised
289 Some objects automatically construct similar objects on any unrecognised
277 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
290 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
278 this will arbitrarily cut off after 100 levels of obj.__wrapped__
291 this will arbitrarily cut off after 100 levels of obj.__wrapped__
279 attribute access. --TK, Jan 2016
292 attribute access. --TK, Jan 2016
280 """
293 """
281 orig_obj = obj
294 orig_obj = obj
282 i = 0
295 i = 0
283 while safe_hasattr(obj, '__wrapped__'):
296 while safe_hasattr(obj, '__wrapped__'):
284 obj = obj.__wrapped__
297 obj = obj.__wrapped__
285 i += 1
298 i += 1
286 if i > 100:
299 if i > 100:
287 # __wrapped__ is probably a lie, so return the thing we started with
300 # __wrapped__ is probably a lie, so return the thing we started with
288 return orig_obj
301 return orig_obj
289 return obj
302 return obj
290
303
291 def find_file(obj) -> str:
304 def find_file(obj) -> str:
292 """Find the absolute path to the file where an object was defined.
305 """Find the absolute path to the file where an object was defined.
293
306
294 This is essentially a robust wrapper around `inspect.getabsfile`.
307 This is essentially a robust wrapper around `inspect.getabsfile`.
295
308
296 Returns None if no file can be found.
309 Returns None if no file can be found.
297
310
298 Parameters
311 Parameters
299 ----------
312 ----------
300 obj : any Python object
313 obj : any Python object
301
314
302 Returns
315 Returns
303 -------
316 -------
304 fname : str
317 fname : str
305 The absolute path to the file where the object was defined.
318 The absolute path to the file where the object was defined.
306 """
319 """
307 obj = _get_wrapped(obj)
320 obj = _get_wrapped(obj)
308
321
309 fname = None
322 fname = None
310 try:
323 try:
311 fname = inspect.getabsfile(obj)
324 fname = inspect.getabsfile(obj)
312 except TypeError:
325 except TypeError:
313 # For an instance, the file that matters is where its class was
326 # For an instance, the file that matters is where its class was
314 # declared.
327 # declared.
315 try:
328 try:
316 fname = inspect.getabsfile(obj.__class__)
329 fname = inspect.getabsfile(obj.__class__)
317 except (OSError, TypeError):
330 except (OSError, TypeError):
318 # Can happen for builtins
331 # Can happen for builtins
319 pass
332 pass
320 except OSError:
333 except OSError:
321 pass
334 pass
322
335
323 return cast_unicode(fname)
336 return cast_unicode(fname)
324
337
325
338
326 def find_source_lines(obj):
339 def find_source_lines(obj):
327 """Find the line number in a file where an object was defined.
340 """Find the line number in a file where an object was defined.
328
341
329 This is essentially a robust wrapper around `inspect.getsourcelines`.
342 This is essentially a robust wrapper around `inspect.getsourcelines`.
330
343
331 Returns None if no file can be found.
344 Returns None if no file can be found.
332
345
333 Parameters
346 Parameters
334 ----------
347 ----------
335 obj : any Python object
348 obj : any Python object
336
349
337 Returns
350 Returns
338 -------
351 -------
339 lineno : int
352 lineno : int
340 The line number where the object definition starts.
353 The line number where the object definition starts.
341 """
354 """
342 obj = _get_wrapped(obj)
355 obj = _get_wrapped(obj)
343
356
344 try:
357 try:
345 lineno = inspect.getsourcelines(obj)[1]
358 lineno = inspect.getsourcelines(obj)[1]
346 except TypeError:
359 except TypeError:
347 # For instances, try the class object like getsource() does
360 # For instances, try the class object like getsource() does
348 try:
361 try:
349 lineno = inspect.getsourcelines(obj.__class__)[1]
362 lineno = inspect.getsourcelines(obj.__class__)[1]
350 except (OSError, TypeError):
363 except (OSError, TypeError):
351 return None
364 return None
352 except OSError:
365 except OSError:
353 return None
366 return None
354
367
355 return lineno
368 return lineno
356
369
357 class Inspector(Colorable):
370 class Inspector(Colorable):
358
371
359 def __init__(self, color_table=InspectColors,
372 def __init__(self, color_table=InspectColors,
360 code_color_table=PyColorize.ANSICodeColors,
373 code_color_table=PyColorize.ANSICodeColors,
361 scheme=None,
374 scheme=None,
362 str_detail_level=0,
375 str_detail_level=0,
363 parent=None, config=None):
376 parent=None, config=None):
364 super(Inspector, self).__init__(parent=parent, config=config)
377 super(Inspector, self).__init__(parent=parent, config=config)
365 self.color_table = color_table
378 self.color_table = color_table
366 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
379 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
367 self.format = self.parser.format
380 self.format = self.parser.format
368 self.str_detail_level = str_detail_level
381 self.str_detail_level = str_detail_level
369 self.set_active_scheme(scheme)
382 self.set_active_scheme(scheme)
370
383
371 def _getdef(self,obj,oname='') -> Union[str,None]:
384 def _getdef(self,obj,oname='') -> Union[str,None]:
372 """Return the call signature for any callable object.
385 """Return the call signature for any callable object.
373
386
374 If any exception is generated, None is returned instead and the
387 If any exception is generated, None is returned instead and the
375 exception is suppressed."""
388 exception is suppressed."""
376 try:
389 try:
377 return _render_signature(signature(obj), oname)
390 return _render_signature(signature(obj), oname)
378 except:
391 except:
379 return None
392 return None
380
393
381 def __head(self,h) -> str:
394 def __head(self,h) -> str:
382 """Return a header string with proper colors."""
395 """Return a header string with proper colors."""
383 return '%s%s%s' % (self.color_table.active_colors.header,h,
396 return '%s%s%s' % (self.color_table.active_colors.header,h,
384 self.color_table.active_colors.normal)
397 self.color_table.active_colors.normal)
385
398
386 def set_active_scheme(self, scheme):
399 def set_active_scheme(self, scheme):
387 if scheme is not None:
400 if scheme is not None:
388 self.color_table.set_active_scheme(scheme)
401 self.color_table.set_active_scheme(scheme)
389 self.parser.color_table.set_active_scheme(scheme)
402 self.parser.color_table.set_active_scheme(scheme)
390
403
391 def noinfo(self, msg, oname):
404 def noinfo(self, msg, oname):
392 """Generic message when no information is found."""
405 """Generic message when no information is found."""
393 print('No %s found' % msg, end=' ')
406 print('No %s found' % msg, end=' ')
394 if oname:
407 if oname:
395 print('for %s' % oname)
408 print('for %s' % oname)
396 else:
409 else:
397 print()
410 print()
398
411
399 def pdef(self, obj, oname=''):
412 def pdef(self, obj, oname=''):
400 """Print the call signature for any callable object.
413 """Print the call signature for any callable object.
401
414
402 If the object is a class, print the constructor information."""
415 If the object is a class, print the constructor information."""
403
416
404 if not callable(obj):
417 if not callable(obj):
405 print('Object is not callable.')
418 print('Object is not callable.')
406 return
419 return
407
420
408 header = ''
421 header = ''
409
422
410 if inspect.isclass(obj):
423 if inspect.isclass(obj):
411 header = self.__head('Class constructor information:\n')
424 header = self.__head('Class constructor information:\n')
412
425
413
426
414 output = self._getdef(obj,oname)
427 output = self._getdef(obj,oname)
415 if output is None:
428 if output is None:
416 self.noinfo('definition header',oname)
429 self.noinfo('definition header',oname)
417 else:
430 else:
418 print(header,self.format(output), end=' ')
431 print(header,self.format(output), end=' ')
419
432
420 # In Python 3, all classes are new-style, so they all have __init__.
433 # In Python 3, all classes are new-style, so they all have __init__.
421 @skip_doctest
434 @skip_doctest
422 def pdoc(self, obj, oname='', formatter=None):
435 def pdoc(self, obj, oname='', formatter=None):
423 """Print the docstring for any object.
436 """Print the docstring for any object.
424
437
425 Optional:
438 Optional:
426 -formatter: a function to run the docstring through for specially
439 -formatter: a function to run the docstring through for specially
427 formatted docstrings.
440 formatted docstrings.
428
441
429 Examples
442 Examples
430 --------
443 --------
431 In [1]: class NoInit:
444 In [1]: class NoInit:
432 ...: pass
445 ...: pass
433
446
434 In [2]: class NoDoc:
447 In [2]: class NoDoc:
435 ...: def __init__(self):
448 ...: def __init__(self):
436 ...: pass
449 ...: pass
437
450
438 In [3]: %pdoc NoDoc
451 In [3]: %pdoc NoDoc
439 No documentation found for NoDoc
452 No documentation found for NoDoc
440
453
441 In [4]: %pdoc NoInit
454 In [4]: %pdoc NoInit
442 No documentation found for NoInit
455 No documentation found for NoInit
443
456
444 In [5]: obj = NoInit()
457 In [5]: obj = NoInit()
445
458
446 In [6]: %pdoc obj
459 In [6]: %pdoc obj
447 No documentation found for obj
460 No documentation found for obj
448
461
449 In [5]: obj2 = NoDoc()
462 In [5]: obj2 = NoDoc()
450
463
451 In [6]: %pdoc obj2
464 In [6]: %pdoc obj2
452 No documentation found for obj2
465 No documentation found for obj2
453 """
466 """
454
467
455 head = self.__head # For convenience
468 head = self.__head # For convenience
456 lines = []
469 lines = []
457 ds = getdoc(obj)
470 ds = getdoc(obj)
458 if formatter:
471 if formatter:
459 ds = formatter(ds).get('plain/text', ds)
472 ds = formatter(ds).get('plain/text', ds)
460 if ds:
473 if ds:
461 lines.append(head("Class docstring:"))
474 lines.append(head("Class docstring:"))
462 lines.append(indent(ds))
475 lines.append(indent(ds))
463 if inspect.isclass(obj) and hasattr(obj, '__init__'):
476 if inspect.isclass(obj) and hasattr(obj, '__init__'):
464 init_ds = getdoc(obj.__init__)
477 init_ds = getdoc(obj.__init__)
465 if init_ds is not None:
478 if init_ds is not None:
466 lines.append(head("Init docstring:"))
479 lines.append(head("Init docstring:"))
467 lines.append(indent(init_ds))
480 lines.append(indent(init_ds))
468 elif hasattr(obj,'__call__'):
481 elif hasattr(obj,'__call__'):
469 call_ds = getdoc(obj.__call__)
482 call_ds = getdoc(obj.__call__)
470 if call_ds:
483 if call_ds:
471 lines.append(head("Call docstring:"))
484 lines.append(head("Call docstring:"))
472 lines.append(indent(call_ds))
485 lines.append(indent(call_ds))
473
486
474 if not lines:
487 if not lines:
475 self.noinfo('documentation',oname)
488 self.noinfo('documentation',oname)
476 else:
489 else:
477 page.page('\n'.join(lines))
490 page.page('\n'.join(lines))
478
491
479 def psource(self, obj, oname=''):
492 def psource(self, obj, oname=''):
480 """Print the source code for an object."""
493 """Print the source code for an object."""
481
494
482 # Flush the source cache because inspect can return out-of-date source
495 # Flush the source cache because inspect can return out-of-date source
483 linecache.checkcache()
496 linecache.checkcache()
484 try:
497 try:
485 src = getsource(obj, oname=oname)
498 src = getsource(obj, oname=oname)
486 except Exception:
499 except Exception:
487 src = None
500 src = None
488
501
489 if src is None:
502 if src is None:
490 self.noinfo('source', oname)
503 self.noinfo('source', oname)
491 else:
504 else:
492 page.page(self.format(src))
505 page.page(self.format(src))
493
506
494 def pfile(self, obj, oname=''):
507 def pfile(self, obj, oname=''):
495 """Show the whole file where an object was defined."""
508 """Show the whole file where an object was defined."""
496
509
497 lineno = find_source_lines(obj)
510 lineno = find_source_lines(obj)
498 if lineno is None:
511 if lineno is None:
499 self.noinfo('file', oname)
512 self.noinfo('file', oname)
500 return
513 return
501
514
502 ofile = find_file(obj)
515 ofile = find_file(obj)
503 # run contents of file through pager starting at line where the object
516 # run contents of file through pager starting at line where the object
504 # is defined, as long as the file isn't binary and is actually on the
517 # is defined, as long as the file isn't binary and is actually on the
505 # filesystem.
518 # filesystem.
506 if ofile.endswith(('.so', '.dll', '.pyd')):
519 if ofile.endswith(('.so', '.dll', '.pyd')):
507 print('File %r is binary, not printing.' % ofile)
520 print('File %r is binary, not printing.' % ofile)
508 elif not os.path.isfile(ofile):
521 elif not os.path.isfile(ofile):
509 print('File %r does not exist, not printing.' % ofile)
522 print('File %r does not exist, not printing.' % ofile)
510 else:
523 else:
511 # Print only text files, not extension binaries. Note that
524 # Print only text files, not extension binaries. Note that
512 # getsourcelines returns lineno with 1-offset and page() uses
525 # getsourcelines returns lineno with 1-offset and page() uses
513 # 0-offset, so we must adjust.
526 # 0-offset, so we must adjust.
514 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
527 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
515
528
516
529
517 def _mime_format(self, text:str, formatter=None) -> dict:
530 def _mime_format(self, text:str, formatter=None) -> dict:
518 """Return a mime bundle representation of the input text.
531 """Return a mime bundle representation of the input text.
519
532
520 - if `formatter` is None, the returned mime bundle has
533 - if `formatter` is None, the returned mime bundle has
521 a ``text/plain`` field, with the input text.
534 a ``text/plain`` field, with the input text.
522 a ``text/html`` field with a ``<pre>`` tag containing the input text.
535 a ``text/html`` field with a ``<pre>`` tag containing the input text.
523
536
524 - if ``formatter`` is not None, it must be a callable transforming the
537 - if ``formatter`` is not None, it must be a callable transforming the
525 input text into a mime bundle. Default values for ``text/plain`` and
538 input text into a mime bundle. Default values for ``text/plain`` and
526 ``text/html`` representations are the ones described above.
539 ``text/html`` representations are the ones described above.
527
540
528 Note:
541 Note:
529
542
530 Formatters returning strings are supported but this behavior is deprecated.
543 Formatters returning strings are supported but this behavior is deprecated.
531
544
532 """
545 """
533 defaults = {
546 defaults = {
534 "text/plain": text,
547 "text/plain": text,
535 "text/html": f"<pre>{html.escape(text)}</pre>",
548 "text/html": f"<pre>{html.escape(text)}</pre>",
536 }
549 }
537
550
538 if formatter is None:
551 if formatter is None:
539 return defaults
552 return defaults
540 else:
553 else:
541 formatted = formatter(text)
554 formatted = formatter(text)
542
555
543 if not isinstance(formatted, dict):
556 if not isinstance(formatted, dict):
544 # Handle the deprecated behavior of a formatter returning
557 # Handle the deprecated behavior of a formatter returning
545 # a string instead of a mime bundle.
558 # a string instead of a mime bundle.
546 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
559 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
547
560
548 else:
561 else:
549 return dict(defaults, **formatted)
562 return dict(defaults, **formatted)
550
563
551
564
552 def format_mime(self, bundle):
565 def format_mime(self, bundle):
553 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
566 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
554 # Format text/plain mimetype
567 # Format text/plain mimetype
555 if isinstance(bundle["text/plain"], (list, tuple)):
568 if isinstance(bundle["text/plain"], (list, tuple)):
556 # bundle['text/plain'] is a list of (head, formatted body) pairs
569 # bundle['text/plain'] is a list of (head, formatted body) pairs
557 lines = []
570 lines = []
558 _len = max(len(h) for h, _ in bundle["text/plain"])
571 _len = max(len(h) for h, _ in bundle["text/plain"])
559
572
560 for head, body in bundle["text/plain"]:
573 for head, body in bundle["text/plain"]:
561 body = body.strip("\n")
574 body = body.strip("\n")
562 delim = "\n" if "\n" in body else " "
575 delim = "\n" if "\n" in body else " "
563 lines.append(
576 lines.append(
564 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
577 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
565 )
578 )
566
579
567 bundle["text/plain"] = "\n".join(lines)
580 bundle["text/plain"] = "\n".join(lines)
568
581
569 # Format the text/html mimetype
582 # Format the text/html mimetype
570 if isinstance(bundle["text/html"], (list, tuple)):
583 if isinstance(bundle["text/html"], (list, tuple)):
571 # bundle['text/html'] is a list of (head, formatted body) pairs
584 # bundle['text/html'] is a list of (head, formatted body) pairs
572 bundle["text/html"] = "\n".join(
585 bundle["text/html"] = "\n".join(
573 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
586 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
574 )
587 )
575 return bundle
588 return bundle
576
589
577 def _append_info_field(
590 def _append_info_field(
578 self, bundle, title: str, key: str, info, omit_sections, formatter
591 self, bundle, title: str, key: str, info, omit_sections, formatter
579 ):
592 ):
580 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
593 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
581 if title in omit_sections or key in omit_sections:
594 if title in omit_sections or key in omit_sections:
582 return
595 return
583 field = info[key]
596 field = info[key]
584 if field is not None:
597 if field is not None:
585 formatted_field = self._mime_format(field, formatter)
598 formatted_field = self._mime_format(field, formatter)
586 bundle["text/plain"].append((title, formatted_field["text/plain"]))
599 bundle["text/plain"].append((title, formatted_field["text/plain"]))
587 bundle["text/html"].append((title, formatted_field["text/html"]))
600 bundle["text/html"].append((title, formatted_field["text/html"]))
588
601
589 def _make_info_unformatted(self, obj, info, formatter, detail_level, omit_sections):
602 def _make_info_unformatted(self, obj, info, formatter, detail_level, omit_sections):
590 """Assemble the mimebundle as unformatted lists of information"""
603 """Assemble the mimebundle as unformatted lists of information"""
591 bundle = {
604 bundle = {
592 "text/plain": [],
605 "text/plain": [],
593 "text/html": [],
606 "text/html": [],
594 }
607 }
595
608
596 # A convenience function to simplify calls below
609 # A convenience function to simplify calls below
597 def append_field(bundle, title: str, key: str, formatter=None):
610 def append_field(bundle, title: str, key: str, formatter=None):
598 self._append_info_field(
611 self._append_info_field(
599 bundle,
612 bundle,
600 title=title,
613 title=title,
601 key=key,
614 key=key,
602 info=info,
615 info=info,
603 omit_sections=omit_sections,
616 omit_sections=omit_sections,
604 formatter=formatter,
617 formatter=formatter,
605 )
618 )
606
619
607 def code_formatter(text):
620 def code_formatter(text):
608 return {
621 return {
609 'text/plain': self.format(text),
622 'text/plain': self.format(text),
610 'text/html': pylight(text)
623 'text/html': pylight(text)
611 }
624 }
612
625
613 if info["isalias"]:
626 if info["isalias"]:
614 append_field(bundle, "Repr", "string_form")
627 append_field(bundle, "Repr", "string_form")
615
628
616 elif info['ismagic']:
629 elif info['ismagic']:
617 if detail_level > 0:
630 if detail_level > 0:
618 append_field(bundle, "Source", "source", code_formatter)
631 append_field(bundle, "Source", "source", code_formatter)
619 else:
632 else:
620 append_field(bundle, "Docstring", "docstring", formatter)
633 append_field(bundle, "Docstring", "docstring", formatter)
621 append_field(bundle, "File", "file")
634 append_field(bundle, "File", "file")
622
635
623 elif info['isclass'] or is_simple_callable(obj):
636 elif info['isclass'] or is_simple_callable(obj):
624 # Functions, methods, classes
637 # Functions, methods, classes
625 append_field(bundle, "Signature", "definition", code_formatter)
638 append_field(bundle, "Signature", "definition", code_formatter)
626 append_field(bundle, "Init signature", "init_definition", code_formatter)
639 append_field(bundle, "Init signature", "init_definition", code_formatter)
627 append_field(bundle, "Docstring", "docstring", formatter)
640 append_field(bundle, "Docstring", "docstring", formatter)
628 if detail_level > 0 and info["source"]:
641 if detail_level > 0 and info["source"]:
629 append_field(bundle, "Source", "source", code_formatter)
642 append_field(bundle, "Source", "source", code_formatter)
630 else:
643 else:
631 append_field(bundle, "Init docstring", "init_docstring", formatter)
644 append_field(bundle, "Init docstring", "init_docstring", formatter)
632
645
633 append_field(bundle, "File", "file")
646 append_field(bundle, "File", "file")
634 append_field(bundle, "Type", "type_name")
647 append_field(bundle, "Type", "type_name")
635 append_field(bundle, "Subclasses", "subclasses")
648 append_field(bundle, "Subclasses", "subclasses")
636
649
637 else:
650 else:
638 # General Python objects
651 # General Python objects
639 append_field(bundle, "Signature", "definition", code_formatter)
652 append_field(bundle, "Signature", "definition", code_formatter)
640 append_field(bundle, "Call signature", "call_def", code_formatter)
653 append_field(bundle, "Call signature", "call_def", code_formatter)
641 append_field(bundle, "Type", "type_name")
654 append_field(bundle, "Type", "type_name")
642 append_field(bundle, "String form", "string_form")
655 append_field(bundle, "String form", "string_form")
643
656
644 # Namespace
657 # Namespace
645 if info["namespace"] != "Interactive":
658 if info["namespace"] != "Interactive":
646 append_field(bundle, "Namespace", "namespace")
659 append_field(bundle, "Namespace", "namespace")
647
660
648 append_field(bundle, "Length", "length")
661 append_field(bundle, "Length", "length")
649 append_field(bundle, "File", "file")
662 append_field(bundle, "File", "file")
650
663
651 # Source or docstring, depending on detail level and whether
664 # Source or docstring, depending on detail level and whether
652 # source found.
665 # source found.
653 if detail_level > 0 and info["source"]:
666 if detail_level > 0 and info["source"]:
654 append_field(bundle, "Source", "source", code_formatter)
667 append_field(bundle, "Source", "source", code_formatter)
655 else:
668 else:
656 append_field(bundle, "Docstring", "docstring", formatter)
669 append_field(bundle, "Docstring", "docstring", formatter)
657
670
658 append_field(bundle, "Class docstring", "class_docstring", formatter)
671 append_field(bundle, "Class docstring", "class_docstring", formatter)
659 append_field(bundle, "Init docstring", "init_docstring", formatter)
672 append_field(bundle, "Init docstring", "init_docstring", formatter)
660 append_field(bundle, "Call docstring", "call_docstring", formatter)
673 append_field(bundle, "Call docstring", "call_docstring", formatter)
661 return bundle
674 return bundle
662
675
663
676
664 def _get_info(
677 def _get_info(
665 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
678 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
666 ):
679 ):
667 """Retrieve an info dict and format it.
680 """Retrieve an info dict and format it.
668
681
669 Parameters
682 Parameters
670 ----------
683 ----------
671 obj : any
684 obj : any
672 Object to inspect and return info from
685 Object to inspect and return info from
673 oname : str (default: ''):
686 oname : str (default: ''):
674 Name of the variable pointing to `obj`.
687 Name of the variable pointing to `obj`.
675 formatter : callable
688 formatter : callable
676 info
689 info
677 already computed information
690 already computed information
678 detail_level : integer
691 detail_level : integer
679 Granularity of detail level, if set to 1, give more information.
692 Granularity of detail level, if set to 1, give more information.
680 omit_sections : container[str]
693 omit_sections : container[str]
681 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
694 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
682 """
695 """
683
696
684 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
697 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
685 bundle = self._make_info_unformatted(
698 bundle = self._make_info_unformatted(
686 obj, info, formatter, detail_level=detail_level, omit_sections=omit_sections
699 obj, info, formatter, detail_level=detail_level, omit_sections=omit_sections
687 )
700 )
688 return self.format_mime(bundle)
701 return self.format_mime(bundle)
689
702
690 def pinfo(
703 def pinfo(
691 self,
704 self,
692 obj,
705 obj,
693 oname="",
706 oname="",
694 formatter=None,
707 formatter=None,
695 info=None,
708 info=None,
696 detail_level=0,
709 detail_level=0,
697 enable_html_pager=True,
710 enable_html_pager=True,
698 omit_sections=(),
711 omit_sections=(),
699 ):
712 ):
700 """Show detailed information about an object.
713 """Show detailed information about an object.
701
714
702 Optional arguments:
715 Optional arguments:
703
716
704 - oname: name of the variable pointing to the object.
717 - oname: name of the variable pointing to the object.
705
718
706 - formatter: callable (optional)
719 - formatter: callable (optional)
707 A special formatter for docstrings.
720 A special formatter for docstrings.
708
721
709 The formatter is a callable that takes a string as an input
722 The formatter is a callable that takes a string as an input
710 and returns either a formatted string or a mime type bundle
723 and returns either a formatted string or a mime type bundle
711 in the form of a dictionary.
724 in the form of a dictionary.
712
725
713 Although the support of custom formatter returning a string
726 Although the support of custom formatter returning a string
714 instead of a mime type bundle is deprecated.
727 instead of a mime type bundle is deprecated.
715
728
716 - info: a structure with some information fields which may have been
729 - info: a structure with some information fields which may have been
717 precomputed already.
730 precomputed already.
718
731
719 - detail_level: if set to 1, more information is given.
732 - detail_level: if set to 1, more information is given.
720
733
721 - omit_sections: set of section keys and titles to omit
734 - omit_sections: set of section keys and titles to omit
722 """
735 """
723 info = self._get_info(
736 info = self._get_info(
724 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
737 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
725 )
738 )
726 if not enable_html_pager:
739 if not enable_html_pager:
727 del info['text/html']
740 del info['text/html']
728 page.page(info)
741 page.page(info)
729
742
730 def _info(self, obj, oname="", info=None, detail_level=0):
743 def _info(self, obj, oname="", info=None, detail_level=0):
731 """
744 """
732 Inspector.info() was likely improperly marked as deprecated
745 Inspector.info() was likely improperly marked as deprecated
733 while only a parameter was deprecated. We "un-deprecate" it.
746 while only a parameter was deprecated. We "un-deprecate" it.
734 """
747 """
735
748
736 warnings.warn(
749 warnings.warn(
737 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
750 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
738 "and the `formatter=` keyword removed. `Inspector._info` is now "
751 "and the `formatter=` keyword removed. `Inspector._info` is now "
739 "an alias, and you can just call `.info()` directly.",
752 "an alias, and you can just call `.info()` directly.",
740 DeprecationWarning,
753 DeprecationWarning,
741 stacklevel=2,
754 stacklevel=2,
742 )
755 )
743 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
756 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
744
757
745 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
758 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
746 """Compute a dict with detailed information about an object.
759 """Compute a dict with detailed information about an object.
747
760
748 Parameters
761 Parameters
749 ----------
762 ----------
750 obj : any
763 obj : any
751 An object to find information about
764 An object to find information about
752 oname : str (default: '')
765 oname : str (default: '')
753 Name of the variable pointing to `obj`.
766 Name of the variable pointing to `obj`.
754 info : (default: None)
767 info : (default: None)
755 A struct (dict like with attr access) with some information fields
768 A struct (dict like with attr access) with some information fields
756 which may have been precomputed already.
769 which may have been precomputed already.
757 detail_level : int (default:0)
770 detail_level : int (default:0)
758 If set to 1, more information is given.
771 If set to 1, more information is given.
759
772
760 Returns
773 Returns
761 -------
774 -------
762 An object info dict with known fields from `info_fields`. Keys are
775 An object info dict with known fields from `info_fields`. Keys are
763 strings, values are string or None.
776 strings, values are string or None.
764 """
777 """
765
778
766 if info is None:
779 if info is None:
767 ismagic = False
780 ismagic = False
768 isalias = False
781 isalias = False
769 ospace = ''
782 ospace = ''
770 else:
783 else:
771 ismagic = info.ismagic
784 ismagic = info.ismagic
772 isalias = info.isalias
785 isalias = info.isalias
773 ospace = info.namespace
786 ospace = info.namespace
774
787
775 # Get docstring, special-casing aliases:
788 # Get docstring, special-casing aliases:
776 if isalias:
789 if isalias:
777 if not callable(obj):
790 if not callable(obj):
778 try:
791 try:
779 ds = "Alias to the system command:\n %s" % obj[1]
792 ds = "Alias to the system command:\n %s" % obj[1]
780 except:
793 except:
781 ds = "Alias: " + str(obj)
794 ds = "Alias: " + str(obj)
782 else:
795 else:
783 ds = "Alias to " + str(obj)
796 ds = "Alias to " + str(obj)
784 if obj.__doc__:
797 if obj.__doc__:
785 ds += "\nDocstring:\n" + obj.__doc__
798 ds += "\nDocstring:\n" + obj.__doc__
786 else:
799 else:
787 ds = getdoc(obj)
800 ds = getdoc(obj)
788 if ds is None:
801 if ds is None:
789 ds = '<no docstring>'
802 ds = '<no docstring>'
790
803
791 # store output in a dict, we initialize it here and fill it as we go
804 # store output in a dict, we initialize it here and fill it as we go
792 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
805 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
793
806
794 string_max = 200 # max size of strings to show (snipped if longer)
807 string_max = 200 # max size of strings to show (snipped if longer)
795 shalf = int((string_max - 5) / 2)
808 shalf = int((string_max - 5) / 2)
796
809
797 if ismagic:
810 if ismagic:
798 out['type_name'] = 'Magic function'
811 out['type_name'] = 'Magic function'
799 elif isalias:
812 elif isalias:
800 out['type_name'] = 'System alias'
813 out['type_name'] = 'System alias'
801 else:
814 else:
802 out['type_name'] = type(obj).__name__
815 out['type_name'] = type(obj).__name__
803
816
804 try:
817 try:
805 bclass = obj.__class__
818 bclass = obj.__class__
806 out['base_class'] = str(bclass)
819 out['base_class'] = str(bclass)
807 except:
820 except:
808 pass
821 pass
809
822
810 # String form, but snip if too long in ? form (full in ??)
823 # String form, but snip if too long in ? form (full in ??)
811 if detail_level >= self.str_detail_level:
824 if detail_level >= self.str_detail_level:
812 try:
825 try:
813 ostr = str(obj)
826 ostr = str(obj)
814 str_head = 'string_form'
827 str_head = 'string_form'
815 if not detail_level and len(ostr)>string_max:
828 if not detail_level and len(ostr)>string_max:
816 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
829 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
817 ostr = ("\n" + " " * len(str_head.expandtabs())).\
830 ostr = ("\n" + " " * len(str_head.expandtabs())).\
818 join(q.strip() for q in ostr.split("\n"))
831 join(q.strip() for q in ostr.split("\n"))
819 out[str_head] = ostr
832 out[str_head] = ostr
820 except:
833 except:
821 pass
834 pass
822
835
823 if ospace:
836 if ospace:
824 out['namespace'] = ospace
837 out['namespace'] = ospace
825
838
826 # Length (for strings and lists)
839 # Length (for strings and lists)
827 try:
840 try:
828 out['length'] = str(len(obj))
841 out['length'] = str(len(obj))
829 except Exception:
842 except Exception:
830 pass
843 pass
831
844
832 # Filename where object was defined
845 # Filename where object was defined
833 binary_file = False
846 binary_file = False
834 fname = find_file(obj)
847 fname = find_file(obj)
835 if fname is None:
848 if fname is None:
836 # if anything goes wrong, we don't want to show source, so it's as
849 # if anything goes wrong, we don't want to show source, so it's as
837 # if the file was binary
850 # if the file was binary
838 binary_file = True
851 binary_file = True
839 else:
852 else:
840 if fname.endswith(('.so', '.dll', '.pyd')):
853 if fname.endswith(('.so', '.dll', '.pyd')):
841 binary_file = True
854 binary_file = True
842 elif fname.endswith('<string>'):
855 elif fname.endswith('<string>'):
843 fname = 'Dynamically generated function. No source code available.'
856 fname = 'Dynamically generated function. No source code available.'
844 out['file'] = compress_user(fname)
857 out['file'] = compress_user(fname)
845
858
846 # Original source code for a callable, class or property.
859 # Original source code for a callable, class or property.
847 if detail_level:
860 if detail_level:
848 # Flush the source cache because inspect can return out-of-date
861 # Flush the source cache because inspect can return out-of-date
849 # source
862 # source
850 linecache.checkcache()
863 linecache.checkcache()
851 try:
864 try:
852 if isinstance(obj, property) or not binary_file:
865 if isinstance(obj, property) or not binary_file:
853 src = getsource(obj, oname)
866 src = getsource(obj, oname)
854 if src is not None:
867 if src is not None:
855 src = src.rstrip()
868 src = src.rstrip()
856 out['source'] = src
869 out['source'] = src
857
870
858 except Exception:
871 except Exception:
859 pass
872 pass
860
873
861 # Add docstring only if no source is to be shown (avoid repetitions).
874 # Add docstring only if no source is to be shown (avoid repetitions).
862 if ds and not self._source_contains_docstring(out.get('source'), ds):
875 if ds and not self._source_contains_docstring(out.get('source'), ds):
863 out['docstring'] = ds
876 out['docstring'] = ds
864
877
865 # Constructor docstring for classes
878 # Constructor docstring for classes
866 if inspect.isclass(obj):
879 if inspect.isclass(obj):
867 out['isclass'] = True
880 out['isclass'] = True
868
881
869 # get the init signature:
882 # get the init signature:
870 try:
883 try:
871 init_def = self._getdef(obj, oname)
884 init_def = self._getdef(obj, oname)
872 except AttributeError:
885 except AttributeError:
873 init_def = None
886 init_def = None
874
887
875 # get the __init__ docstring
888 # get the __init__ docstring
876 try:
889 try:
877 obj_init = obj.__init__
890 obj_init = obj.__init__
878 except AttributeError:
891 except AttributeError:
879 init_ds = None
892 init_ds = None
880 else:
893 else:
881 if init_def is None:
894 if init_def is None:
882 # Get signature from init if top-level sig failed.
895 # Get signature from init if top-level sig failed.
883 # Can happen for built-in types (list, etc.).
896 # Can happen for built-in types (list, etc.).
884 try:
897 try:
885 init_def = self._getdef(obj_init, oname)
898 init_def = self._getdef(obj_init, oname)
886 except AttributeError:
899 except AttributeError:
887 pass
900 pass
888 init_ds = getdoc(obj_init)
901 init_ds = getdoc(obj_init)
889 # Skip Python's auto-generated docstrings
902 # Skip Python's auto-generated docstrings
890 if init_ds == _object_init_docstring:
903 if init_ds == _object_init_docstring:
891 init_ds = None
904 init_ds = None
892
905
893 if init_def:
906 if init_def:
894 out['init_definition'] = init_def
907 out['init_definition'] = init_def
895
908
896 if init_ds:
909 if init_ds:
897 out['init_docstring'] = init_ds
910 out['init_docstring'] = init_ds
898
911
899 names = [sub.__name__ for sub in type.__subclasses__(obj)]
912 names = [sub.__name__ for sub in type.__subclasses__(obj)]
900 if len(names) < 10:
913 if len(names) < 10:
901 all_names = ', '.join(names)
914 all_names = ', '.join(names)
902 else:
915 else:
903 all_names = ', '.join(names[:10]+['...'])
916 all_names = ', '.join(names[:10]+['...'])
904 out['subclasses'] = all_names
917 out['subclasses'] = all_names
905 # and class docstring for instances:
918 # and class docstring for instances:
906 else:
919 else:
907 # reconstruct the function definition and print it:
920 # reconstruct the function definition and print it:
908 defln = self._getdef(obj, oname)
921 defln = self._getdef(obj, oname)
909 if defln:
922 if defln:
910 out['definition'] = defln
923 out['definition'] = defln
911
924
912 # First, check whether the instance docstring is identical to the
925 # First, check whether the instance docstring is identical to the
913 # class one, and print it separately if they don't coincide. In
926 # class one, and print it separately if they don't coincide. In
914 # most cases they will, but it's nice to print all the info for
927 # most cases they will, but it's nice to print all the info for
915 # objects which use instance-customized docstrings.
928 # objects which use instance-customized docstrings.
916 if ds:
929 if ds:
917 try:
930 try:
918 cls = getattr(obj,'__class__')
931 cls = getattr(obj,'__class__')
919 except:
932 except:
920 class_ds = None
933 class_ds = None
921 else:
934 else:
922 class_ds = getdoc(cls)
935 class_ds = getdoc(cls)
923 # Skip Python's auto-generated docstrings
936 # Skip Python's auto-generated docstrings
924 if class_ds in _builtin_type_docstrings:
937 if class_ds in _builtin_type_docstrings:
925 class_ds = None
938 class_ds = None
926 if class_ds and ds != class_ds:
939 if class_ds and ds != class_ds:
927 out['class_docstring'] = class_ds
940 out['class_docstring'] = class_ds
928
941
929 # Next, try to show constructor docstrings
942 # Next, try to show constructor docstrings
930 try:
943 try:
931 init_ds = getdoc(obj.__init__)
944 init_ds = getdoc(obj.__init__)
932 # Skip Python's auto-generated docstrings
945 # Skip Python's auto-generated docstrings
933 if init_ds == _object_init_docstring:
946 if init_ds == _object_init_docstring:
934 init_ds = None
947 init_ds = None
935 except AttributeError:
948 except AttributeError:
936 init_ds = None
949 init_ds = None
937 if init_ds:
950 if init_ds:
938 out['init_docstring'] = init_ds
951 out['init_docstring'] = init_ds
939
952
940 # Call form docstring for callable instances
953 # Call form docstring for callable instances
941 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
954 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
942 call_def = self._getdef(obj.__call__, oname)
955 call_def = self._getdef(obj.__call__, oname)
943 if call_def and (call_def != out.get('definition')):
956 if call_def and (call_def != out.get('definition')):
944 # it may never be the case that call def and definition differ,
957 # it may never be the case that call def and definition differ,
945 # but don't include the same signature twice
958 # but don't include the same signature twice
946 out['call_def'] = call_def
959 out['call_def'] = call_def
947 call_ds = getdoc(obj.__call__)
960 call_ds = getdoc(obj.__call__)
948 # Skip Python's auto-generated docstrings
961 # Skip Python's auto-generated docstrings
949 if call_ds == _func_call_docstring:
962 if call_ds == _func_call_docstring:
950 call_ds = None
963 call_ds = None
951 if call_ds:
964 if call_ds:
952 out['call_docstring'] = call_ds
965 out['call_docstring'] = call_ds
953
966
954 return object_info(**out)
967 return object_info(**out)
955
968
956 @staticmethod
969 @staticmethod
957 def _source_contains_docstring(src, doc):
970 def _source_contains_docstring(src, doc):
958 """
971 """
959 Check whether the source *src* contains the docstring *doc*.
972 Check whether the source *src* contains the docstring *doc*.
960
973
961 This is is helper function to skip displaying the docstring if the
974 This is is helper function to skip displaying the docstring if the
962 source already contains it, avoiding repetition of information.
975 source already contains it, avoiding repetition of information.
963 """
976 """
964 try:
977 try:
965 def_node, = ast.parse(dedent(src)).body
978 def_node, = ast.parse(dedent(src)).body
966 return ast.get_docstring(def_node) == doc
979 return ast.get_docstring(def_node) == doc
967 except Exception:
980 except Exception:
968 # The source can become invalid or even non-existent (because it
981 # The source can become invalid or even non-existent (because it
969 # is re-fetched from the source file) so the above code fail in
982 # is re-fetched from the source file) so the above code fail in
970 # arbitrary ways.
983 # arbitrary ways.
971 return False
984 return False
972
985
973 def psearch(self,pattern,ns_table,ns_search=[],
986 def psearch(self,pattern,ns_table,ns_search=[],
974 ignore_case=False,show_all=False, *, list_types=False):
987 ignore_case=False,show_all=False, *, list_types=False):
975 """Search namespaces with wildcards for objects.
988 """Search namespaces with wildcards for objects.
976
989
977 Arguments:
990 Arguments:
978
991
979 - pattern: string containing shell-like wildcards to use in namespace
992 - pattern: string containing shell-like wildcards to use in namespace
980 searches and optionally a type specification to narrow the search to
993 searches and optionally a type specification to narrow the search to
981 objects of that type.
994 objects of that type.
982
995
983 - ns_table: dict of name->namespaces for search.
996 - ns_table: dict of name->namespaces for search.
984
997
985 Optional arguments:
998 Optional arguments:
986
999
987 - ns_search: list of namespace names to include in search.
1000 - ns_search: list of namespace names to include in search.
988
1001
989 - ignore_case(False): make the search case-insensitive.
1002 - ignore_case(False): make the search case-insensitive.
990
1003
991 - show_all(False): show all names, including those starting with
1004 - show_all(False): show all names, including those starting with
992 underscores.
1005 underscores.
993
1006
994 - list_types(False): list all available object types for object matching.
1007 - list_types(False): list all available object types for object matching.
995 """
1008 """
996 #print 'ps pattern:<%r>' % pattern # dbg
1009 #print 'ps pattern:<%r>' % pattern # dbg
997
1010
998 # defaults
1011 # defaults
999 type_pattern = 'all'
1012 type_pattern = 'all'
1000 filter = ''
1013 filter = ''
1001
1014
1002 # list all object types
1015 # list all object types
1003 if list_types:
1016 if list_types:
1004 page.page('\n'.join(sorted(typestr2type)))
1017 page.page('\n'.join(sorted(typestr2type)))
1005 return
1018 return
1006
1019
1007 cmds = pattern.split()
1020 cmds = pattern.split()
1008 len_cmds = len(cmds)
1021 len_cmds = len(cmds)
1009 if len_cmds == 1:
1022 if len_cmds == 1:
1010 # Only filter pattern given
1023 # Only filter pattern given
1011 filter = cmds[0]
1024 filter = cmds[0]
1012 elif len_cmds == 2:
1025 elif len_cmds == 2:
1013 # Both filter and type specified
1026 # Both filter and type specified
1014 filter,type_pattern = cmds
1027 filter,type_pattern = cmds
1015 else:
1028 else:
1016 raise ValueError('invalid argument string for psearch: <%s>' %
1029 raise ValueError('invalid argument string for psearch: <%s>' %
1017 pattern)
1030 pattern)
1018
1031
1019 # filter search namespaces
1032 # filter search namespaces
1020 for name in ns_search:
1033 for name in ns_search:
1021 if name not in ns_table:
1034 if name not in ns_table:
1022 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1035 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1023 (name,ns_table.keys()))
1036 (name,ns_table.keys()))
1024
1037
1025 #print 'type_pattern:',type_pattern # dbg
1038 #print 'type_pattern:',type_pattern # dbg
1026 search_result, namespaces_seen = set(), set()
1039 search_result, namespaces_seen = set(), set()
1027 for ns_name in ns_search:
1040 for ns_name in ns_search:
1028 ns = ns_table[ns_name]
1041 ns = ns_table[ns_name]
1029 # Normally, locals and globals are the same, so we just check one.
1042 # Normally, locals and globals are the same, so we just check one.
1030 if id(ns) in namespaces_seen:
1043 if id(ns) in namespaces_seen:
1031 continue
1044 continue
1032 namespaces_seen.add(id(ns))
1045 namespaces_seen.add(id(ns))
1033 tmp_res = list_namespace(ns, type_pattern, filter,
1046 tmp_res = list_namespace(ns, type_pattern, filter,
1034 ignore_case=ignore_case, show_all=show_all)
1047 ignore_case=ignore_case, show_all=show_all)
1035 search_result.update(tmp_res)
1048 search_result.update(tmp_res)
1036
1049
1037 page.page('\n'.join(sorted(search_result)))
1050 page.page('\n'.join(sorted(search_result)))
1038
1051
1039
1052
1040 def _render_signature(obj_signature, obj_name) -> str:
1053 def _render_signature(obj_signature, obj_name) -> str:
1041 """
1054 """
1042 This was mostly taken from inspect.Signature.__str__.
1055 This was mostly taken from inspect.Signature.__str__.
1043 Look there for the comments.
1056 Look there for the comments.
1044 The only change is to add linebreaks when this gets too long.
1057 The only change is to add linebreaks when this gets too long.
1045 """
1058 """
1046 result = []
1059 result = []
1047 pos_only = False
1060 pos_only = False
1048 kw_only = True
1061 kw_only = True
1049 for param in obj_signature.parameters.values():
1062 for param in obj_signature.parameters.values():
1050 if param.kind == inspect._POSITIONAL_ONLY:
1063 if param.kind == inspect._POSITIONAL_ONLY:
1051 pos_only = True
1064 pos_only = True
1052 elif pos_only:
1065 elif pos_only:
1053 result.append('/')
1066 result.append('/')
1054 pos_only = False
1067 pos_only = False
1055
1068
1056 if param.kind == inspect._VAR_POSITIONAL:
1069 if param.kind == inspect._VAR_POSITIONAL:
1057 kw_only = False
1070 kw_only = False
1058 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1071 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1059 result.append('*')
1072 result.append('*')
1060 kw_only = False
1073 kw_only = False
1061
1074
1062 result.append(str(param))
1075 result.append(str(param))
1063
1076
1064 if pos_only:
1077 if pos_only:
1065 result.append('/')
1078 result.append('/')
1066
1079
1067 # add up name, parameters, braces (2), and commas
1080 # add up name, parameters, braces (2), and commas
1068 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1081 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1069 # This doesn’t fit behind “Signature: ” in an inspect window.
1082 # This doesn’t fit behind “Signature: ” in an inspect window.
1070 rendered = '{}(\n{})'.format(obj_name, ''.join(
1083 rendered = '{}(\n{})'.format(obj_name, ''.join(
1071 ' {},\n'.format(r) for r in result)
1084 ' {},\n'.format(r) for r in result)
1072 )
1085 )
1073 else:
1086 else:
1074 rendered = '{}({})'.format(obj_name, ', '.join(result))
1087 rendered = '{}({})'.format(obj_name, ', '.join(result))
1075
1088
1076 if obj_signature.return_annotation is not inspect._empty:
1089 if obj_signature.return_annotation is not inspect._empty:
1077 anno = inspect.formatannotation(obj_signature.return_annotation)
1090 anno = inspect.formatannotation(obj_signature.return_annotation)
1078 rendered += ' -> {}'.format(anno)
1091 rendered += ' -> {}'.format(anno)
1079
1092
1080 return rendered
1093 return rendered
@@ -1,698 +1,700 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Prefiltering components.
3 Prefiltering components.
4
4
5 Prefilters transform user input before it is exec'd by Python. These
5 Prefilters transform user input before it is exec'd by Python. These
6 transforms are used to implement additional syntax such as !ls and %magic.
6 transforms are used to implement additional syntax such as !ls and %magic.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 from keyword import iskeyword
12 from keyword import iskeyword
13 import re
13 import re
14
14
15 from .autocall import IPyAutocall
15 from .autocall import IPyAutocall
16 from traitlets.config.configurable import Configurable
16 from traitlets.config.configurable import Configurable
17 from .inputtransformer2 import (
17 from .inputtransformer2 import (
18 ESC_MAGIC,
18 ESC_MAGIC,
19 ESC_QUOTE,
19 ESC_QUOTE,
20 ESC_QUOTE2,
20 ESC_QUOTE2,
21 ESC_PAREN,
21 ESC_PAREN,
22 )
22 )
23 from .macro import Macro
23 from .macro import Macro
24 from .splitinput import LineInfo
24 from .splitinput import LineInfo
25
25
26 from traitlets import (
26 from traitlets import (
27 List, Integer, Unicode, Bool, Instance, CRegExp
27 List, Integer, Unicode, Bool, Instance, CRegExp
28 )
28 )
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Global utilities, errors and constants
31 # Global utilities, errors and constants
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class PrefilterError(Exception):
35 class PrefilterError(Exception):
36 pass
36 pass
37
37
38
38
39 # RegExp to identify potential function names
39 # RegExp to identify potential function names
40 re_fun_name = re.compile(r'[^\W\d]([\w.]*) *$')
40 re_fun_name = re.compile(r'[^\W\d]([\w.]*) *$')
41
41
42 # RegExp to exclude strings with this start from autocalling. In
42 # RegExp to exclude strings with this start from autocalling. In
43 # particular, all binary operators should be excluded, so that if foo is
43 # particular, all binary operators should be excluded, so that if foo is
44 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
44 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
45 # characters '!=()' don't need to be checked for, as the checkPythonChars
45 # characters '!=()' don't need to be checked for, as the checkPythonChars
46 # routine explicitly does so, to catch direct calls and rebindings of
46 # routine explicitly does so, to catch direct calls and rebindings of
47 # existing names.
47 # existing names.
48
48
49 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
49 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
50 # it affects the rest of the group in square brackets.
50 # it affects the rest of the group in square brackets.
51 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
51 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
52 r'|^is |^not |^in |^and |^or ')
52 r'|^is |^not |^in |^and |^or ')
53
53
54 # try to catch also methods for stuff in lists/tuples/dicts: off
54 # try to catch also methods for stuff in lists/tuples/dicts: off
55 # (experimental). For this to work, the line_split regexp would need
55 # (experimental). For this to work, the line_split regexp would need
56 # to be modified so it wouldn't break things at '['. That line is
56 # to be modified so it wouldn't break things at '['. That line is
57 # nasty enough that I shouldn't change it until I can test it _well_.
57 # nasty enough that I shouldn't change it until I can test it _well_.
58 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
58 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
59
59
60
60
61 # Handler Check Utilities
61 # Handler Check Utilities
62 def is_shadowed(identifier, ip):
62 def is_shadowed(identifier, ip):
63 """Is the given identifier defined in one of the namespaces which shadow
63 """Is the given identifier defined in one of the namespaces which shadow
64 the alias and magic namespaces? Note that an identifier is different
64 the alias and magic namespaces? Note that an identifier is different
65 than ifun, because it can not contain a '.' character."""
65 than ifun, because it can not contain a '.' character."""
66 # This is much safer than calling ofind, which can change state
66 # This is much safer than calling ofind, which can change state
67 return (identifier in ip.user_ns \
67 return (identifier in ip.user_ns \
68 or identifier in ip.user_global_ns \
68 or identifier in ip.user_global_ns \
69 or identifier in ip.ns_table['builtin']\
69 or identifier in ip.ns_table['builtin']\
70 or iskeyword(identifier))
70 or iskeyword(identifier))
71
71
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Main Prefilter manager
74 # Main Prefilter manager
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77
77
78 class PrefilterManager(Configurable):
78 class PrefilterManager(Configurable):
79 """Main prefilter component.
79 """Main prefilter component.
80
80
81 The IPython prefilter is run on all user input before it is run. The
81 The IPython prefilter is run on all user input before it is run. The
82 prefilter consumes lines of input and produces transformed lines of
82 prefilter consumes lines of input and produces transformed lines of
83 input.
83 input.
84
84
85 The implementation consists of two phases:
85 The implementation consists of two phases:
86
86
87 1. Transformers
87 1. Transformers
88 2. Checkers and handlers
88 2. Checkers and handlers
89
89
90 Over time, we plan on deprecating the checkers and handlers and doing
90 Over time, we plan on deprecating the checkers and handlers and doing
91 everything in the transformers.
91 everything in the transformers.
92
92
93 The transformers are instances of :class:`PrefilterTransformer` and have
93 The transformers are instances of :class:`PrefilterTransformer` and have
94 a single method :meth:`transform` that takes a line and returns a
94 a single method :meth:`transform` that takes a line and returns a
95 transformed line. The transformation can be accomplished using any
95 transformed line. The transformation can be accomplished using any
96 tool, but our current ones use regular expressions for speed.
96 tool, but our current ones use regular expressions for speed.
97
97
98 After all the transformers have been run, the line is fed to the checkers,
98 After all the transformers have been run, the line is fed to the checkers,
99 which are instances of :class:`PrefilterChecker`. The line is passed to
99 which are instances of :class:`PrefilterChecker`. The line is passed to
100 the :meth:`check` method, which either returns `None` or a
100 the :meth:`check` method, which either returns `None` or a
101 :class:`PrefilterHandler` instance. If `None` is returned, the other
101 :class:`PrefilterHandler` instance. If `None` is returned, the other
102 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
102 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
103 the line is passed to the :meth:`handle` method of the returned
103 the line is passed to the :meth:`handle` method of the returned
104 handler and no further checkers are tried.
104 handler and no further checkers are tried.
105
105
106 Both transformers and checkers have a `priority` attribute, that determines
106 Both transformers and checkers have a `priority` attribute, that determines
107 the order in which they are called. Smaller priorities are tried first.
107 the order in which they are called. Smaller priorities are tried first.
108
108
109 Both transformers and checkers also have `enabled` attribute, which is
109 Both transformers and checkers also have `enabled` attribute, which is
110 a boolean that determines if the instance is used.
110 a boolean that determines if the instance is used.
111
111
112 Users or developers can change the priority or enabled attribute of
112 Users or developers can change the priority or enabled attribute of
113 transformers or checkers, but they must call the :meth:`sort_checkers`
113 transformers or checkers, but they must call the :meth:`sort_checkers`
114 or :meth:`sort_transformers` method after changing the priority.
114 or :meth:`sort_transformers` method after changing the priority.
115 """
115 """
116
116
117 multi_line_specials = Bool(True).tag(config=True)
117 multi_line_specials = Bool(True).tag(config=True)
118 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
118 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
119
119
120 def __init__(self, shell=None, **kwargs):
120 def __init__(self, shell=None, **kwargs):
121 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
121 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
122 self.shell = shell
122 self.shell = shell
123 self._transformers = []
123 self._transformers = []
124 self.init_handlers()
124 self.init_handlers()
125 self.init_checkers()
125 self.init_checkers()
126
126
127 #-------------------------------------------------------------------------
127 #-------------------------------------------------------------------------
128 # API for managing transformers
128 # API for managing transformers
129 #-------------------------------------------------------------------------
129 #-------------------------------------------------------------------------
130
130
131 def sort_transformers(self):
131 def sort_transformers(self):
132 """Sort the transformers by priority.
132 """Sort the transformers by priority.
133
133
134 This must be called after the priority of a transformer is changed.
134 This must be called after the priority of a transformer is changed.
135 The :meth:`register_transformer` method calls this automatically.
135 The :meth:`register_transformer` method calls this automatically.
136 """
136 """
137 self._transformers.sort(key=lambda x: x.priority)
137 self._transformers.sort(key=lambda x: x.priority)
138
138
139 @property
139 @property
140 def transformers(self):
140 def transformers(self):
141 """Return a list of checkers, sorted by priority."""
141 """Return a list of checkers, sorted by priority."""
142 return self._transformers
142 return self._transformers
143
143
144 def register_transformer(self, transformer):
144 def register_transformer(self, transformer):
145 """Register a transformer instance."""
145 """Register a transformer instance."""
146 if transformer not in self._transformers:
146 if transformer not in self._transformers:
147 self._transformers.append(transformer)
147 self._transformers.append(transformer)
148 self.sort_transformers()
148 self.sort_transformers()
149
149
150 def unregister_transformer(self, transformer):
150 def unregister_transformer(self, transformer):
151 """Unregister a transformer instance."""
151 """Unregister a transformer instance."""
152 if transformer in self._transformers:
152 if transformer in self._transformers:
153 self._transformers.remove(transformer)
153 self._transformers.remove(transformer)
154
154
155 #-------------------------------------------------------------------------
155 #-------------------------------------------------------------------------
156 # API for managing checkers
156 # API for managing checkers
157 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
158
158
159 def init_checkers(self):
159 def init_checkers(self):
160 """Create the default checkers."""
160 """Create the default checkers."""
161 self._checkers = []
161 self._checkers = []
162 for checker in _default_checkers:
162 for checker in _default_checkers:
163 checker(
163 checker(
164 shell=self.shell, prefilter_manager=self, parent=self
164 shell=self.shell, prefilter_manager=self, parent=self
165 )
165 )
166
166
167 def sort_checkers(self):
167 def sort_checkers(self):
168 """Sort the checkers by priority.
168 """Sort the checkers by priority.
169
169
170 This must be called after the priority of a checker is changed.
170 This must be called after the priority of a checker is changed.
171 The :meth:`register_checker` method calls this automatically.
171 The :meth:`register_checker` method calls this automatically.
172 """
172 """
173 self._checkers.sort(key=lambda x: x.priority)
173 self._checkers.sort(key=lambda x: x.priority)
174
174
175 @property
175 @property
176 def checkers(self):
176 def checkers(self):
177 """Return a list of checkers, sorted by priority."""
177 """Return a list of checkers, sorted by priority."""
178 return self._checkers
178 return self._checkers
179
179
180 def register_checker(self, checker):
180 def register_checker(self, checker):
181 """Register a checker instance."""
181 """Register a checker instance."""
182 if checker not in self._checkers:
182 if checker not in self._checkers:
183 self._checkers.append(checker)
183 self._checkers.append(checker)
184 self.sort_checkers()
184 self.sort_checkers()
185
185
186 def unregister_checker(self, checker):
186 def unregister_checker(self, checker):
187 """Unregister a checker instance."""
187 """Unregister a checker instance."""
188 if checker in self._checkers:
188 if checker in self._checkers:
189 self._checkers.remove(checker)
189 self._checkers.remove(checker)
190
190
191 #-------------------------------------------------------------------------
191 #-------------------------------------------------------------------------
192 # API for managing handlers
192 # API for managing handlers
193 #-------------------------------------------------------------------------
193 #-------------------------------------------------------------------------
194
194
195 def init_handlers(self):
195 def init_handlers(self):
196 """Create the default handlers."""
196 """Create the default handlers."""
197 self._handlers = {}
197 self._handlers = {}
198 self._esc_handlers = {}
198 self._esc_handlers = {}
199 for handler in _default_handlers:
199 for handler in _default_handlers:
200 handler(
200 handler(
201 shell=self.shell, prefilter_manager=self, parent=self
201 shell=self.shell, prefilter_manager=self, parent=self
202 )
202 )
203
203
204 @property
204 @property
205 def handlers(self):
205 def handlers(self):
206 """Return a dict of all the handlers."""
206 """Return a dict of all the handlers."""
207 return self._handlers
207 return self._handlers
208
208
209 def register_handler(self, name, handler, esc_strings):
209 def register_handler(self, name, handler, esc_strings):
210 """Register a handler instance by name with esc_strings."""
210 """Register a handler instance by name with esc_strings."""
211 self._handlers[name] = handler
211 self._handlers[name] = handler
212 for esc_str in esc_strings:
212 for esc_str in esc_strings:
213 self._esc_handlers[esc_str] = handler
213 self._esc_handlers[esc_str] = handler
214
214
215 def unregister_handler(self, name, handler, esc_strings):
215 def unregister_handler(self, name, handler, esc_strings):
216 """Unregister a handler instance by name with esc_strings."""
216 """Unregister a handler instance by name with esc_strings."""
217 try:
217 try:
218 del self._handlers[name]
218 del self._handlers[name]
219 except KeyError:
219 except KeyError:
220 pass
220 pass
221 for esc_str in esc_strings:
221 for esc_str in esc_strings:
222 h = self._esc_handlers.get(esc_str)
222 h = self._esc_handlers.get(esc_str)
223 if h is handler:
223 if h is handler:
224 del self._esc_handlers[esc_str]
224 del self._esc_handlers[esc_str]
225
225
226 def get_handler_by_name(self, name):
226 def get_handler_by_name(self, name):
227 """Get a handler by its name."""
227 """Get a handler by its name."""
228 return self._handlers.get(name)
228 return self._handlers.get(name)
229
229
230 def get_handler_by_esc(self, esc_str):
230 def get_handler_by_esc(self, esc_str):
231 """Get a handler by its escape string."""
231 """Get a handler by its escape string."""
232 return self._esc_handlers.get(esc_str)
232 return self._esc_handlers.get(esc_str)
233
233
234 #-------------------------------------------------------------------------
234 #-------------------------------------------------------------------------
235 # Main prefiltering API
235 # Main prefiltering API
236 #-------------------------------------------------------------------------
236 #-------------------------------------------------------------------------
237
237
238 def prefilter_line_info(self, line_info):
238 def prefilter_line_info(self, line_info):
239 """Prefilter a line that has been converted to a LineInfo object.
239 """Prefilter a line that has been converted to a LineInfo object.
240
240
241 This implements the checker/handler part of the prefilter pipe.
241 This implements the checker/handler part of the prefilter pipe.
242 """
242 """
243 # print "prefilter_line_info: ", line_info
243 # print "prefilter_line_info: ", line_info
244 handler = self.find_handler(line_info)
244 handler = self.find_handler(line_info)
245 return handler.handle(line_info)
245 return handler.handle(line_info)
246
246
247 def find_handler(self, line_info):
247 def find_handler(self, line_info):
248 """Find a handler for the line_info by trying checkers."""
248 """Find a handler for the line_info by trying checkers."""
249 for checker in self.checkers:
249 for checker in self.checkers:
250 if checker.enabled:
250 if checker.enabled:
251 handler = checker.check(line_info)
251 handler = checker.check(line_info)
252 if handler:
252 if handler:
253 return handler
253 return handler
254 return self.get_handler_by_name('normal')
254 return self.get_handler_by_name('normal')
255
255
256 def transform_line(self, line, continue_prompt):
256 def transform_line(self, line, continue_prompt):
257 """Calls the enabled transformers in order of increasing priority."""
257 """Calls the enabled transformers in order of increasing priority."""
258 for transformer in self.transformers:
258 for transformer in self.transformers:
259 if transformer.enabled:
259 if transformer.enabled:
260 line = transformer.transform(line, continue_prompt)
260 line = transformer.transform(line, continue_prompt)
261 return line
261 return line
262
262
263 def prefilter_line(self, line, continue_prompt=False):
263 def prefilter_line(self, line, continue_prompt=False):
264 """Prefilter a single input line as text.
264 """Prefilter a single input line as text.
265
265
266 This method prefilters a single line of text by calling the
266 This method prefilters a single line of text by calling the
267 transformers and then the checkers/handlers.
267 transformers and then the checkers/handlers.
268 """
268 """
269
269
270 # print "prefilter_line: ", line, continue_prompt
270 # print "prefilter_line: ", line, continue_prompt
271 # All handlers *must* return a value, even if it's blank ('').
271 # All handlers *must* return a value, even if it's blank ('').
272
272
273 # save the line away in case we crash, so the post-mortem handler can
273 # save the line away in case we crash, so the post-mortem handler can
274 # record it
274 # record it
275 self.shell._last_input_line = line
275 self.shell._last_input_line = line
276
276
277 if not line:
277 if not line:
278 # Return immediately on purely empty lines, so that if the user
278 # Return immediately on purely empty lines, so that if the user
279 # previously typed some whitespace that started a continuation
279 # previously typed some whitespace that started a continuation
280 # prompt, he can break out of that loop with just an empty line.
280 # prompt, he can break out of that loop with just an empty line.
281 # This is how the default python prompt works.
281 # This is how the default python prompt works.
282 return ''
282 return ''
283
283
284 # At this point, we invoke our transformers.
284 # At this point, we invoke our transformers.
285 if not continue_prompt or (continue_prompt and self.multi_line_specials):
285 if not continue_prompt or (continue_prompt and self.multi_line_specials):
286 line = self.transform_line(line, continue_prompt)
286 line = self.transform_line(line, continue_prompt)
287
287
288 # Now we compute line_info for the checkers and handlers
288 # Now we compute line_info for the checkers and handlers
289 line_info = LineInfo(line, continue_prompt)
289 line_info = LineInfo(line, continue_prompt)
290
290
291 # the input history needs to track even empty lines
291 # the input history needs to track even empty lines
292 stripped = line.strip()
292 stripped = line.strip()
293
293
294 normal_handler = self.get_handler_by_name('normal')
294 normal_handler = self.get_handler_by_name('normal')
295 if not stripped:
295 if not stripped:
296 return normal_handler.handle(line_info)
296 return normal_handler.handle(line_info)
297
297
298 # special handlers are only allowed for single line statements
298 # special handlers are only allowed for single line statements
299 if continue_prompt and not self.multi_line_specials:
299 if continue_prompt and not self.multi_line_specials:
300 return normal_handler.handle(line_info)
300 return normal_handler.handle(line_info)
301
301
302 prefiltered = self.prefilter_line_info(line_info)
302 prefiltered = self.prefilter_line_info(line_info)
303 # print "prefiltered line: %r" % prefiltered
303 # print "prefiltered line: %r" % prefiltered
304 return prefiltered
304 return prefiltered
305
305
306 def prefilter_lines(self, lines, continue_prompt=False):
306 def prefilter_lines(self, lines, continue_prompt=False):
307 """Prefilter multiple input lines of text.
307 """Prefilter multiple input lines of text.
308
308
309 This is the main entry point for prefiltering multiple lines of
309 This is the main entry point for prefiltering multiple lines of
310 input. This simply calls :meth:`prefilter_line` for each line of
310 input. This simply calls :meth:`prefilter_line` for each line of
311 input.
311 input.
312
312
313 This covers cases where there are multiple lines in the user entry,
313 This covers cases where there are multiple lines in the user entry,
314 which is the case when the user goes back to a multiline history
314 which is the case when the user goes back to a multiline history
315 entry and presses enter.
315 entry and presses enter.
316 """
316 """
317 llines = lines.rstrip('\n').split('\n')
317 llines = lines.rstrip('\n').split('\n')
318 # We can get multiple lines in one shot, where multiline input 'blends'
318 # We can get multiple lines in one shot, where multiline input 'blends'
319 # into one line, in cases like recalling from the readline history
319 # into one line, in cases like recalling from the readline history
320 # buffer. We need to make sure that in such cases, we correctly
320 # buffer. We need to make sure that in such cases, we correctly
321 # communicate downstream which line is first and which are continuation
321 # communicate downstream which line is first and which are continuation
322 # ones.
322 # ones.
323 if len(llines) > 1:
323 if len(llines) > 1:
324 out = '\n'.join([self.prefilter_line(line, lnum>0)
324 out = '\n'.join([self.prefilter_line(line, lnum>0)
325 for lnum, line in enumerate(llines) ])
325 for lnum, line in enumerate(llines) ])
326 else:
326 else:
327 out = self.prefilter_line(llines[0], continue_prompt)
327 out = self.prefilter_line(llines[0], continue_prompt)
328
328
329 return out
329 return out
330
330
331 #-----------------------------------------------------------------------------
331 #-----------------------------------------------------------------------------
332 # Prefilter transformers
332 # Prefilter transformers
333 #-----------------------------------------------------------------------------
333 #-----------------------------------------------------------------------------
334
334
335
335
336 class PrefilterTransformer(Configurable):
336 class PrefilterTransformer(Configurable):
337 """Transform a line of user input."""
337 """Transform a line of user input."""
338
338
339 priority = Integer(100).tag(config=True)
339 priority = Integer(100).tag(config=True)
340 # Transformers don't currently use shell or prefilter_manager, but as we
340 # Transformers don't currently use shell or prefilter_manager, but as we
341 # move away from checkers and handlers, they will need them.
341 # move away from checkers and handlers, they will need them.
342 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
342 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
343 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
343 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
344 enabled = Bool(True).tag(config=True)
344 enabled = Bool(True).tag(config=True)
345
345
346 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
346 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
347 super(PrefilterTransformer, self).__init__(
347 super(PrefilterTransformer, self).__init__(
348 shell=shell, prefilter_manager=prefilter_manager, **kwargs
348 shell=shell, prefilter_manager=prefilter_manager, **kwargs
349 )
349 )
350 self.prefilter_manager.register_transformer(self)
350 self.prefilter_manager.register_transformer(self)
351
351
352 def transform(self, line, continue_prompt):
352 def transform(self, line, continue_prompt):
353 """Transform a line, returning the new one."""
353 """Transform a line, returning the new one."""
354 return None
354 return None
355
355
356 def __repr__(self):
356 def __repr__(self):
357 return "<%s(priority=%r, enabled=%r)>" % (
357 return "<%s(priority=%r, enabled=%r)>" % (
358 self.__class__.__name__, self.priority, self.enabled)
358 self.__class__.__name__, self.priority, self.enabled)
359
359
360
360
361 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362 # Prefilter checkers
362 # Prefilter checkers
363 #-----------------------------------------------------------------------------
363 #-----------------------------------------------------------------------------
364
364
365
365
366 class PrefilterChecker(Configurable):
366 class PrefilterChecker(Configurable):
367 """Inspect an input line and return a handler for that line."""
367 """Inspect an input line and return a handler for that line."""
368
368
369 priority = Integer(100).tag(config=True)
369 priority = Integer(100).tag(config=True)
370 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
370 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
371 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
371 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
372 enabled = Bool(True).tag(config=True)
372 enabled = Bool(True).tag(config=True)
373
373
374 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
374 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
375 super(PrefilterChecker, self).__init__(
375 super(PrefilterChecker, self).__init__(
376 shell=shell, prefilter_manager=prefilter_manager, **kwargs
376 shell=shell, prefilter_manager=prefilter_manager, **kwargs
377 )
377 )
378 self.prefilter_manager.register_checker(self)
378 self.prefilter_manager.register_checker(self)
379
379
380 def check(self, line_info):
380 def check(self, line_info):
381 """Inspect line_info and return a handler instance or None."""
381 """Inspect line_info and return a handler instance or None."""
382 return None
382 return None
383
383
384 def __repr__(self):
384 def __repr__(self):
385 return "<%s(priority=%r, enabled=%r)>" % (
385 return "<%s(priority=%r, enabled=%r)>" % (
386 self.__class__.__name__, self.priority, self.enabled)
386 self.__class__.__name__, self.priority, self.enabled)
387
387
388
388
389 class EmacsChecker(PrefilterChecker):
389 class EmacsChecker(PrefilterChecker):
390
390
391 priority = Integer(100).tag(config=True)
391 priority = Integer(100).tag(config=True)
392 enabled = Bool(False).tag(config=True)
392 enabled = Bool(False).tag(config=True)
393
393
394 def check(self, line_info):
394 def check(self, line_info):
395 "Emacs ipython-mode tags certain input lines."
395 "Emacs ipython-mode tags certain input lines."
396 if line_info.line.endswith('# PYTHON-MODE'):
396 if line_info.line.endswith('# PYTHON-MODE'):
397 return self.prefilter_manager.get_handler_by_name('emacs')
397 return self.prefilter_manager.get_handler_by_name('emacs')
398 else:
398 else:
399 return None
399 return None
400
400
401
401
402 class MacroChecker(PrefilterChecker):
402 class MacroChecker(PrefilterChecker):
403
403
404 priority = Integer(250).tag(config=True)
404 priority = Integer(250).tag(config=True)
405
405
406 def check(self, line_info):
406 def check(self, line_info):
407 obj = self.shell.user_ns.get(line_info.ifun)
407 obj = self.shell.user_ns.get(line_info.ifun)
408 if isinstance(obj, Macro):
408 if isinstance(obj, Macro):
409 return self.prefilter_manager.get_handler_by_name('macro')
409 return self.prefilter_manager.get_handler_by_name('macro')
410 else:
410 else:
411 return None
411 return None
412
412
413
413
414 class IPyAutocallChecker(PrefilterChecker):
414 class IPyAutocallChecker(PrefilterChecker):
415
415
416 priority = Integer(300).tag(config=True)
416 priority = Integer(300).tag(config=True)
417
417
418 def check(self, line_info):
418 def check(self, line_info):
419 "Instances of IPyAutocall in user_ns get autocalled immediately"
419 "Instances of IPyAutocall in user_ns get autocalled immediately"
420 obj = self.shell.user_ns.get(line_info.ifun, None)
420 obj = self.shell.user_ns.get(line_info.ifun, None)
421 if isinstance(obj, IPyAutocall):
421 if isinstance(obj, IPyAutocall):
422 obj.set_ip(self.shell)
422 obj.set_ip(self.shell)
423 return self.prefilter_manager.get_handler_by_name('auto')
423 return self.prefilter_manager.get_handler_by_name('auto')
424 else:
424 else:
425 return None
425 return None
426
426
427
427
428 class AssignmentChecker(PrefilterChecker):
428 class AssignmentChecker(PrefilterChecker):
429
429
430 priority = Integer(600).tag(config=True)
430 priority = Integer(600).tag(config=True)
431
431
432 def check(self, line_info):
432 def check(self, line_info):
433 """Check to see if user is assigning to a var for the first time, in
433 """Check to see if user is assigning to a var for the first time, in
434 which case we want to avoid any sort of automagic / autocall games.
434 which case we want to avoid any sort of automagic / autocall games.
435
435
436 This allows users to assign to either alias or magic names true python
436 This allows users to assign to either alias or magic names true python
437 variables (the magic/alias systems always take second seat to true
437 variables (the magic/alias systems always take second seat to true
438 python code). E.g. ls='hi', or ls,that=1,2"""
438 python code). E.g. ls='hi', or ls,that=1,2"""
439 if line_info.the_rest:
439 if line_info.the_rest:
440 if line_info.the_rest[0] in '=,':
440 if line_info.the_rest[0] in '=,':
441 return self.prefilter_manager.get_handler_by_name('normal')
441 return self.prefilter_manager.get_handler_by_name('normal')
442 else:
442 else:
443 return None
443 return None
444
444
445
445
446 class AutoMagicChecker(PrefilterChecker):
446 class AutoMagicChecker(PrefilterChecker):
447
447
448 priority = Integer(700).tag(config=True)
448 priority = Integer(700).tag(config=True)
449
449
450 def check(self, line_info):
450 def check(self, line_info):
451 """If the ifun is magic, and automagic is on, run it. Note: normal,
451 """If the ifun is magic, and automagic is on, run it. Note: normal,
452 non-auto magic would already have been triggered via '%' in
452 non-auto magic would already have been triggered via '%' in
453 check_esc_chars. This just checks for automagic. Also, before
453 check_esc_chars. This just checks for automagic. Also, before
454 triggering the magic handler, make sure that there is nothing in the
454 triggering the magic handler, make sure that there is nothing in the
455 user namespace which could shadow it."""
455 user namespace which could shadow it."""
456 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
456 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
457 return None
457 return None
458
458
459 # We have a likely magic method. Make sure we should actually call it.
459 # We have a likely magic method. Make sure we should actually call it.
460 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
460 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
461 return None
461 return None
462
462
463 head = line_info.ifun.split('.',1)[0]
463 head = line_info.ifun.split('.',1)[0]
464 if is_shadowed(head, self.shell):
464 if is_shadowed(head, self.shell):
465 return None
465 return None
466
466
467 return self.prefilter_manager.get_handler_by_name('magic')
467 return self.prefilter_manager.get_handler_by_name('magic')
468
468
469
469
470 class PythonOpsChecker(PrefilterChecker):
470 class PythonOpsChecker(PrefilterChecker):
471
471
472 priority = Integer(900).tag(config=True)
472 priority = Integer(900).tag(config=True)
473
473
474 def check(self, line_info):
474 def check(self, line_info):
475 """If the 'rest' of the line begins with a function call or pretty much
475 """If the 'rest' of the line begins with a function call or pretty much
476 any python operator, we should simply execute the line (regardless of
476 any python operator, we should simply execute the line (regardless of
477 whether or not there's a possible autocall expansion). This avoids
477 whether or not there's a possible autocall expansion). This avoids
478 spurious (and very confusing) geattr() accesses."""
478 spurious (and very confusing) geattr() accesses."""
479 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
479 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
480 return self.prefilter_manager.get_handler_by_name('normal')
480 return self.prefilter_manager.get_handler_by_name('normal')
481 else:
481 else:
482 return None
482 return None
483
483
484
484
485 class AutocallChecker(PrefilterChecker):
485 class AutocallChecker(PrefilterChecker):
486
486
487 priority = Integer(1000).tag(config=True)
487 priority = Integer(1000).tag(config=True)
488
488
489 function_name_regexp = CRegExp(re_fun_name,
489 function_name_regexp = CRegExp(re_fun_name,
490 help="RegExp to identify potential function names."
490 help="RegExp to identify potential function names."
491 ).tag(config=True)
491 ).tag(config=True)
492 exclude_regexp = CRegExp(re_exclude_auto,
492 exclude_regexp = CRegExp(re_exclude_auto,
493 help="RegExp to exclude strings with this start from autocalling."
493 help="RegExp to exclude strings with this start from autocalling."
494 ).tag(config=True)
494 ).tag(config=True)
495
495
496 def check(self, line_info):
496 def check(self, line_info):
497 "Check if the initial word/function is callable and autocall is on."
497 "Check if the initial word/function is callable and autocall is on."
498 if not self.shell.autocall:
498 if not self.shell.autocall:
499 return None
499 return None
500
500
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
501 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
502 if not oinfo['found']:
502 if not oinfo.found:
503 return None
503 return None
504
504
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
505 ignored_funs = ['b', 'f', 'r', 'u', 'br', 'rb', 'fr', 'rf']
506 ifun = line_info.ifun
506 ifun = line_info.ifun
507 line = line_info.line
507 line = line_info.line
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
508 if ifun.lower() in ignored_funs and (line.startswith(ifun + "'") or line.startswith(ifun + '"')):
509 return None
509 return None
510
510
511 if callable(oinfo['obj']) \
511 if (
512 and (not self.exclude_regexp.match(line_info.the_rest)) \
512 callable(oinfo.obj)
513 and self.function_name_regexp.match(line_info.ifun):
513 and (not self.exclude_regexp.match(line_info.the_rest))
514 return self.prefilter_manager.get_handler_by_name('auto')
514 and self.function_name_regexp.match(line_info.ifun)
515 ):
516 return self.prefilter_manager.get_handler_by_name("auto")
515 else:
517 else:
516 return None
518 return None
517
519
518
520
519 #-----------------------------------------------------------------------------
521 #-----------------------------------------------------------------------------
520 # Prefilter handlers
522 # Prefilter handlers
521 #-----------------------------------------------------------------------------
523 #-----------------------------------------------------------------------------
522
524
523
525
524 class PrefilterHandler(Configurable):
526 class PrefilterHandler(Configurable):
525
527
526 handler_name = Unicode('normal')
528 handler_name = Unicode('normal')
527 esc_strings = List([])
529 esc_strings = List([])
528 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
530 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
529 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
531 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
530
532
531 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
533 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
532 super(PrefilterHandler, self).__init__(
534 super(PrefilterHandler, self).__init__(
533 shell=shell, prefilter_manager=prefilter_manager, **kwargs
535 shell=shell, prefilter_manager=prefilter_manager, **kwargs
534 )
536 )
535 self.prefilter_manager.register_handler(
537 self.prefilter_manager.register_handler(
536 self.handler_name,
538 self.handler_name,
537 self,
539 self,
538 self.esc_strings
540 self.esc_strings
539 )
541 )
540
542
541 def handle(self, line_info):
543 def handle(self, line_info):
542 # print "normal: ", line_info
544 # print "normal: ", line_info
543 """Handle normal input lines. Use as a template for handlers."""
545 """Handle normal input lines. Use as a template for handlers."""
544
546
545 # With autoindent on, we need some way to exit the input loop, and I
547 # With autoindent on, we need some way to exit the input loop, and I
546 # don't want to force the user to have to backspace all the way to
548 # don't want to force the user to have to backspace all the way to
547 # clear the line. The rule will be in this case, that either two
549 # clear the line. The rule will be in this case, that either two
548 # lines of pure whitespace in a row, or a line of pure whitespace but
550 # lines of pure whitespace in a row, or a line of pure whitespace but
549 # of a size different to the indent level, will exit the input loop.
551 # of a size different to the indent level, will exit the input loop.
550 line = line_info.line
552 line = line_info.line
551 continue_prompt = line_info.continue_prompt
553 continue_prompt = line_info.continue_prompt
552
554
553 if (continue_prompt and
555 if (continue_prompt and
554 self.shell.autoindent and
556 self.shell.autoindent and
555 line.isspace() and
557 line.isspace() and
556 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
558 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
557 line = ''
559 line = ''
558
560
559 return line
561 return line
560
562
561 def __str__(self):
563 def __str__(self):
562 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
564 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
563
565
564
566
565 class MacroHandler(PrefilterHandler):
567 class MacroHandler(PrefilterHandler):
566 handler_name = Unicode("macro")
568 handler_name = Unicode("macro")
567
569
568 def handle(self, line_info):
570 def handle(self, line_info):
569 obj = self.shell.user_ns.get(line_info.ifun)
571 obj = self.shell.user_ns.get(line_info.ifun)
570 pre_space = line_info.pre_whitespace
572 pre_space = line_info.pre_whitespace
571 line_sep = "\n" + pre_space
573 line_sep = "\n" + pre_space
572 return pre_space + line_sep.join(obj.value.splitlines())
574 return pre_space + line_sep.join(obj.value.splitlines())
573
575
574
576
575 class MagicHandler(PrefilterHandler):
577 class MagicHandler(PrefilterHandler):
576
578
577 handler_name = Unicode('magic')
579 handler_name = Unicode('magic')
578 esc_strings = List([ESC_MAGIC])
580 esc_strings = List([ESC_MAGIC])
579
581
580 def handle(self, line_info):
582 def handle(self, line_info):
581 """Execute magic functions."""
583 """Execute magic functions."""
582 ifun = line_info.ifun
584 ifun = line_info.ifun
583 the_rest = line_info.the_rest
585 the_rest = line_info.the_rest
584 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
586 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
585 t_arg_s = ifun + " " + the_rest
587 t_arg_s = ifun + " " + the_rest
586 t_magic_name, _, t_magic_arg_s = t_arg_s.partition(' ')
588 t_magic_name, _, t_magic_arg_s = t_arg_s.partition(' ')
587 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
589 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
588 cmd = '%sget_ipython().run_line_magic(%r, %r)' % (line_info.pre_whitespace, t_magic_name, t_magic_arg_s)
590 cmd = '%sget_ipython().run_line_magic(%r, %r)' % (line_info.pre_whitespace, t_magic_name, t_magic_arg_s)
589 return cmd
591 return cmd
590
592
591
593
592 class AutoHandler(PrefilterHandler):
594 class AutoHandler(PrefilterHandler):
593
595
594 handler_name = Unicode('auto')
596 handler_name = Unicode('auto')
595 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
597 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
596
598
597 def handle(self, line_info):
599 def handle(self, line_info):
598 """Handle lines which can be auto-executed, quoting if requested."""
600 """Handle lines which can be auto-executed, quoting if requested."""
599 line = line_info.line
601 line = line_info.line
600 ifun = line_info.ifun
602 ifun = line_info.ifun
601 the_rest = line_info.the_rest
603 the_rest = line_info.the_rest
602 esc = line_info.esc
604 esc = line_info.esc
603 continue_prompt = line_info.continue_prompt
605 continue_prompt = line_info.continue_prompt
604 obj = line_info.ofind(self.shell)['obj']
606 obj = line_info.ofind(self.shell).obj
605
607
606 # This should only be active for single-line input!
608 # This should only be active for single-line input!
607 if continue_prompt:
609 if continue_prompt:
608 return line
610 return line
609
611
610 force_auto = isinstance(obj, IPyAutocall)
612 force_auto = isinstance(obj, IPyAutocall)
611
613
612 # User objects sometimes raise exceptions on attribute access other
614 # User objects sometimes raise exceptions on attribute access other
613 # than AttributeError (we've seen it in the past), so it's safest to be
615 # than AttributeError (we've seen it in the past), so it's safest to be
614 # ultra-conservative here and catch all.
616 # ultra-conservative here and catch all.
615 try:
617 try:
616 auto_rewrite = obj.rewrite
618 auto_rewrite = obj.rewrite
617 except Exception:
619 except Exception:
618 auto_rewrite = True
620 auto_rewrite = True
619
621
620 if esc == ESC_QUOTE:
622 if esc == ESC_QUOTE:
621 # Auto-quote splitting on whitespace
623 # Auto-quote splitting on whitespace
622 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
624 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
623 elif esc == ESC_QUOTE2:
625 elif esc == ESC_QUOTE2:
624 # Auto-quote whole string
626 # Auto-quote whole string
625 newcmd = '%s("%s")' % (ifun,the_rest)
627 newcmd = '%s("%s")' % (ifun,the_rest)
626 elif esc == ESC_PAREN:
628 elif esc == ESC_PAREN:
627 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
629 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
628 else:
630 else:
629 # Auto-paren.
631 # Auto-paren.
630 if force_auto:
632 if force_auto:
631 # Don't rewrite if it is already a call.
633 # Don't rewrite if it is already a call.
632 do_rewrite = not the_rest.startswith('(')
634 do_rewrite = not the_rest.startswith('(')
633 else:
635 else:
634 if not the_rest:
636 if not the_rest:
635 # We only apply it to argument-less calls if the autocall
637 # We only apply it to argument-less calls if the autocall
636 # parameter is set to 2.
638 # parameter is set to 2.
637 do_rewrite = (self.shell.autocall >= 2)
639 do_rewrite = (self.shell.autocall >= 2)
638 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
640 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
639 # Don't autocall in this case: item access for an object
641 # Don't autocall in this case: item access for an object
640 # which is BOTH callable and implements __getitem__.
642 # which is BOTH callable and implements __getitem__.
641 do_rewrite = False
643 do_rewrite = False
642 else:
644 else:
643 do_rewrite = True
645 do_rewrite = True
644
646
645 # Figure out the rewritten command
647 # Figure out the rewritten command
646 if do_rewrite:
648 if do_rewrite:
647 if the_rest.endswith(';'):
649 if the_rest.endswith(';'):
648 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
650 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
649 else:
651 else:
650 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
652 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
651 else:
653 else:
652 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
654 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
653 return normal_handler.handle(line_info)
655 return normal_handler.handle(line_info)
654
656
655 # Display the rewritten call
657 # Display the rewritten call
656 if auto_rewrite:
658 if auto_rewrite:
657 self.shell.auto_rewrite_input(newcmd)
659 self.shell.auto_rewrite_input(newcmd)
658
660
659 return newcmd
661 return newcmd
660
662
661
663
662 class EmacsHandler(PrefilterHandler):
664 class EmacsHandler(PrefilterHandler):
663
665
664 handler_name = Unicode('emacs')
666 handler_name = Unicode('emacs')
665 esc_strings = List([])
667 esc_strings = List([])
666
668
667 def handle(self, line_info):
669 def handle(self, line_info):
668 """Handle input lines marked by python-mode."""
670 """Handle input lines marked by python-mode."""
669
671
670 # Currently, nothing is done. Later more functionality can be added
672 # Currently, nothing is done. Later more functionality can be added
671 # here if needed.
673 # here if needed.
672
674
673 # The input cache shouldn't be updated
675 # The input cache shouldn't be updated
674 return line_info.line
676 return line_info.line
675
677
676
678
677 #-----------------------------------------------------------------------------
679 #-----------------------------------------------------------------------------
678 # Defaults
680 # Defaults
679 #-----------------------------------------------------------------------------
681 #-----------------------------------------------------------------------------
680
682
681
683
682 _default_checkers = [
684 _default_checkers = [
683 EmacsChecker,
685 EmacsChecker,
684 MacroChecker,
686 MacroChecker,
685 IPyAutocallChecker,
687 IPyAutocallChecker,
686 AssignmentChecker,
688 AssignmentChecker,
687 AutoMagicChecker,
689 AutoMagicChecker,
688 PythonOpsChecker,
690 PythonOpsChecker,
689 AutocallChecker
691 AutocallChecker
690 ]
692 ]
691
693
692 _default_handlers = [
694 _default_handlers = [
693 PrefilterHandler,
695 PrefilterHandler,
694 MacroHandler,
696 MacroHandler,
695 MagicHandler,
697 MagicHandler,
696 AutoHandler,
698 AutoHandler,
697 EmacsHandler
699 EmacsHandler
698 ]
700 ]
@@ -1,137 +1,138 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Simple utility for splitting user input. This is used by both inputsplitter and
3 Simple utility for splitting user input. This is used by both inputsplitter and
4 prefilter.
4 prefilter.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2008-2011 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import re
23 import re
24 import sys
24 import sys
25
25
26 from IPython.utils import py3compat
26 from IPython.utils import py3compat
27 from IPython.utils.encoding import get_stream_enc
27 from IPython.utils.encoding import get_stream_enc
28 from IPython.core.oinspect import OInfo
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Main function
31 # Main function
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32
33
33 # RegExp for splitting line contents into pre-char//first word-method//rest.
34 # RegExp for splitting line contents into pre-char//first word-method//rest.
34 # For clarity, each group in on one line.
35 # For clarity, each group in on one line.
35
36
36 # WARNING: update the regexp if the escapes in interactiveshell are changed, as
37 # WARNING: update the regexp if the escapes in interactiveshell are changed, as
37 # they are hardwired in.
38 # they are hardwired in.
38
39
39 # Although it's not solely driven by the regex, note that:
40 # Although it's not solely driven by the regex, note that:
40 # ,;/% only trigger if they are the first character on the line
41 # ,;/% only trigger if they are the first character on the line
41 # ! and !! trigger if they are first char(s) *or* follow an indent
42 # ! and !! trigger if they are first char(s) *or* follow an indent
42 # ? triggers as first or last char.
43 # ? triggers as first or last char.
43
44
44 line_split = re.compile(r"""
45 line_split = re.compile(r"""
45 ^(\s*) # any leading space
46 ^(\s*) # any leading space
46 ([,;/%]|!!?|\?\??)? # escape character or characters
47 ([,;/%]|!!?|\?\??)? # escape character or characters
47 \s*(%{0,2}[\w\.\*]*) # function/method, possibly with leading %
48 \s*(%{0,2}[\w\.\*]*) # function/method, possibly with leading %
48 # to correctly treat things like '?%magic'
49 # to correctly treat things like '?%magic'
49 (.*?$|$) # rest of line
50 (.*?$|$) # rest of line
50 """, re.VERBOSE)
51 """, re.VERBOSE)
51
52
52
53
53 def split_user_input(line, pattern=None):
54 def split_user_input(line, pattern=None):
54 """Split user input into initial whitespace, escape character, function part
55 """Split user input into initial whitespace, escape character, function part
55 and the rest.
56 and the rest.
56 """
57 """
57 # We need to ensure that the rest of this routine deals only with unicode
58 # We need to ensure that the rest of this routine deals only with unicode
58 encoding = get_stream_enc(sys.stdin, 'utf-8')
59 encoding = get_stream_enc(sys.stdin, 'utf-8')
59 line = py3compat.cast_unicode(line, encoding)
60 line = py3compat.cast_unicode(line, encoding)
60
61
61 if pattern is None:
62 if pattern is None:
62 pattern = line_split
63 pattern = line_split
63 match = pattern.match(line)
64 match = pattern.match(line)
64 if not match:
65 if not match:
65 # print "match failed for line '%s'" % line
66 # print "match failed for line '%s'" % line
66 try:
67 try:
67 ifun, the_rest = line.split(None,1)
68 ifun, the_rest = line.split(None,1)
68 except ValueError:
69 except ValueError:
69 # print "split failed for line '%s'" % line
70 # print "split failed for line '%s'" % line
70 ifun, the_rest = line, u''
71 ifun, the_rest = line, u''
71 pre = re.match(r'^(\s*)(.*)',line).groups()[0]
72 pre = re.match(r'^(\s*)(.*)',line).groups()[0]
72 esc = ""
73 esc = ""
73 else:
74 else:
74 pre, esc, ifun, the_rest = match.groups()
75 pre, esc, ifun, the_rest = match.groups()
75
76
76 #print 'line:<%s>' % line # dbg
77 #print 'line:<%s>' % line # dbg
77 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest) # dbg
78 #print 'pre <%s> ifun <%s> rest <%s>' % (pre,ifun.strip(),the_rest) # dbg
78 return pre, esc or '', ifun.strip(), the_rest.lstrip()
79 return pre, esc or '', ifun.strip(), the_rest.lstrip()
79
80
80
81
81 class LineInfo(object):
82 class LineInfo(object):
82 """A single line of input and associated info.
83 """A single line of input and associated info.
83
84
84 Includes the following as properties:
85 Includes the following as properties:
85
86
86 line
87 line
87 The original, raw line
88 The original, raw line
88
89
89 continue_prompt
90 continue_prompt
90 Is this line a continuation in a sequence of multiline input?
91 Is this line a continuation in a sequence of multiline input?
91
92
92 pre
93 pre
93 Any leading whitespace.
94 Any leading whitespace.
94
95
95 esc
96 esc
96 The escape character(s) in pre or the empty string if there isn't one.
97 The escape character(s) in pre or the empty string if there isn't one.
97 Note that '!!' and '??' are possible values for esc. Otherwise it will
98 Note that '!!' and '??' are possible values for esc. Otherwise it will
98 always be a single character.
99 always be a single character.
99
100
100 ifun
101 ifun
101 The 'function part', which is basically the maximal initial sequence
102 The 'function part', which is basically the maximal initial sequence
102 of valid python identifiers and the '.' character. This is what is
103 of valid python identifiers and the '.' character. This is what is
103 checked for alias and magic transformations, used for auto-calling,
104 checked for alias and magic transformations, used for auto-calling,
104 etc. In contrast to Python identifiers, it may start with "%" and contain
105 etc. In contrast to Python identifiers, it may start with "%" and contain
105 "*".
106 "*".
106
107
107 the_rest
108 the_rest
108 Everything else on the line.
109 Everything else on the line.
109 """
110 """
110 def __init__(self, line, continue_prompt=False):
111 def __init__(self, line, continue_prompt=False):
111 self.line = line
112 self.line = line
112 self.continue_prompt = continue_prompt
113 self.continue_prompt = continue_prompt
113 self.pre, self.esc, self.ifun, self.the_rest = split_user_input(line)
114 self.pre, self.esc, self.ifun, self.the_rest = split_user_input(line)
114
115
115 self.pre_char = self.pre.strip()
116 self.pre_char = self.pre.strip()
116 if self.pre_char:
117 if self.pre_char:
117 self.pre_whitespace = '' # No whitespace allowed before esc chars
118 self.pre_whitespace = '' # No whitespace allowed before esc chars
118 else:
119 else:
119 self.pre_whitespace = self.pre
120 self.pre_whitespace = self.pre
120
121
121 def ofind(self, ip):
122 def ofind(self, ip) -> OInfo:
122 """Do a full, attribute-walking lookup of the ifun in the various
123 """Do a full, attribute-walking lookup of the ifun in the various
123 namespaces for the given IPython InteractiveShell instance.
124 namespaces for the given IPython InteractiveShell instance.
124
125
125 Return a dict with keys: {found, obj, ospace, ismagic}
126 Return a dict with keys: {found, obj, ospace, ismagic}
126
127
127 Note: can cause state changes because of calling getattr, but should
128 Note: can cause state changes because of calling getattr, but should
128 only be run if autocall is on and if the line hasn't matched any
129 only be run if autocall is on and if the line hasn't matched any
129 other, less dangerous handlers.
130 other, less dangerous handlers.
130
131
131 Does cache the results of the call, so can be called multiple times
132 Does cache the results of the call, so can be called multiple times
132 without worrying about *further* damaging state.
133 without worrying about *further* damaging state.
133 """
134 """
134 return ip._ofind(self.ifun)
135 return ip._ofind(self.ifun)
135
136
136 def __str__(self):
137 def __str__(self):
137 return "LineInfo [%s|%s|%s|%s]" %(self.pre, self.esc, self.ifun, self.the_rest)
138 return "LineInfo [%s|%s|%s|%s]" %(self.pre, self.esc, self.ifun, self.the_rest)
@@ -1,193 +1,193 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for completerlib.
2 """Tests for completerlib.
3
3
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import os
10 import os
11 import shutil
11 import shutil
12 import sys
12 import sys
13 import tempfile
13 import tempfile
14 import unittest
14 import unittest
15 from os.path import join
15 from os.path import join
16
16
17 from tempfile import TemporaryDirectory
17 from tempfile import TemporaryDirectory
18
18
19 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
19 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
20 from IPython.testing.decorators import onlyif_unicode_paths
20 from IPython.testing.decorators import onlyif_unicode_paths
21
21
22
22
23 class MockEvent(object):
23 class MockEvent(object):
24 def __init__(self, line):
24 def __init__(self, line):
25 self.line = line
25 self.line = line
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Test functions begin
28 # Test functions begin
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 class Test_magic_run_completer(unittest.TestCase):
30 class Test_magic_run_completer(unittest.TestCase):
31 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
31 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
32 dirs = [u"adir/", "bdir/"]
32 dirs = [u"adir/", "bdir/"]
33
33
34 def setUp(self):
34 def setUp(self):
35 self.BASETESTDIR = tempfile.mkdtemp()
35 self.BASETESTDIR = tempfile.mkdtemp()
36 for fil in self.files:
36 for fil in self.files:
37 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
37 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
38 sfile.write("pass\n")
38 sfile.write("pass\n")
39 for d in self.dirs:
39 for d in self.dirs:
40 os.mkdir(join(self.BASETESTDIR, d))
40 os.mkdir(join(self.BASETESTDIR, d))
41
41
42 self.oldpath = os.getcwd()
42 self.oldpath = os.getcwd()
43 os.chdir(self.BASETESTDIR)
43 os.chdir(self.BASETESTDIR)
44
44
45 def tearDown(self):
45 def tearDown(self):
46 os.chdir(self.oldpath)
46 os.chdir(self.oldpath)
47 shutil.rmtree(self.BASETESTDIR)
47 shutil.rmtree(self.BASETESTDIR)
48
48
49 def test_1(self):
49 def test_1(self):
50 """Test magic_run_completer, should match two alternatives
50 """Test magic_run_completer, should match two alternatives
51 """
51 """
52 event = MockEvent(u"%run a")
52 event = MockEvent(u"%run a")
53 mockself = None
53 mockself = None
54 match = set(magic_run_completer(mockself, event))
54 match = set(magic_run_completer(mockself, event))
55 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
55 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
56
56
57 def test_2(self):
57 def test_2(self):
58 """Test magic_run_completer, should match one alternative
58 """Test magic_run_completer, should match one alternative
59 """
59 """
60 event = MockEvent(u"%run aa")
60 event = MockEvent(u"%run aa")
61 mockself = None
61 mockself = None
62 match = set(magic_run_completer(mockself, event))
62 match = set(magic_run_completer(mockself, event))
63 self.assertEqual(match, {u"aao.py"})
63 self.assertEqual(match, {u"aao.py"})
64
64
65 def test_3(self):
65 def test_3(self):
66 """Test magic_run_completer with unterminated " """
66 """Test magic_run_completer with unterminated " """
67 event = MockEvent(u'%run "a')
67 event = MockEvent(u'%run "a')
68 mockself = None
68 mockself = None
69 match = set(magic_run_completer(mockself, event))
69 match = set(magic_run_completer(mockself, event))
70 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
70 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
71
71
72 def test_completion_more_args(self):
72 def test_completion_more_args(self):
73 event = MockEvent(u'%run a.py ')
73 event = MockEvent(u'%run a.py ')
74 match = set(magic_run_completer(None, event))
74 match = set(magic_run_completer(None, event))
75 self.assertEqual(match, set(self.files + self.dirs))
75 self.assertEqual(match, set(self.files + self.dirs))
76
76
77 def test_completion_in_dir(self):
77 def test_completion_in_dir(self):
78 # Github issue #3459
78 # Github issue #3459
79 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
79 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
80 print(repr(event.line))
80 print(repr(event.line))
81 match = set(magic_run_completer(None, event))
81 match = set(magic_run_completer(None, event))
82 # We specifically use replace here rather than normpath, because
82 # We specifically use replace here rather than normpath, because
83 # at one point there were duplicates 'adir' and 'adir/', and normpath
83 # at one point there were duplicates 'adir' and 'adir/', and normpath
84 # would hide the failure for that.
84 # would hide the failure for that.
85 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
85 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
86 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
86 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
87
87
88 class Test_magic_run_completer_nonascii(unittest.TestCase):
88 class Test_magic_run_completer_nonascii(unittest.TestCase):
89 @onlyif_unicode_paths
89 @onlyif_unicode_paths
90 def setUp(self):
90 def setUp(self):
91 self.BASETESTDIR = tempfile.mkdtemp()
91 self.BASETESTDIR = tempfile.mkdtemp()
92 for fil in [u"aaø.py", u"a.py", u"b.py"]:
92 for fil in [u"aaø.py", u"a.py", u"b.py"]:
93 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
93 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
94 sfile.write("pass\n")
94 sfile.write("pass\n")
95 self.oldpath = os.getcwd()
95 self.oldpath = os.getcwd()
96 os.chdir(self.BASETESTDIR)
96 os.chdir(self.BASETESTDIR)
97
97
98 def tearDown(self):
98 def tearDown(self):
99 os.chdir(self.oldpath)
99 os.chdir(self.oldpath)
100 shutil.rmtree(self.BASETESTDIR)
100 shutil.rmtree(self.BASETESTDIR)
101
101
102 @onlyif_unicode_paths
102 @onlyif_unicode_paths
103 def test_1(self):
103 def test_1(self):
104 """Test magic_run_completer, should match two alternatives
104 """Test magic_run_completer, should match two alternatives
105 """
105 """
106 event = MockEvent(u"%run a")
106 event = MockEvent(u"%run a")
107 mockself = None
107 mockself = None
108 match = set(magic_run_completer(mockself, event))
108 match = set(magic_run_completer(mockself, event))
109 self.assertEqual(match, {u"a.py", u"aaø.py"})
109 self.assertEqual(match, {u"a.py", u"aaø.py"})
110
110
111 @onlyif_unicode_paths
111 @onlyif_unicode_paths
112 def test_2(self):
112 def test_2(self):
113 """Test magic_run_completer, should match one alternative
113 """Test magic_run_completer, should match one alternative
114 """
114 """
115 event = MockEvent(u"%run aa")
115 event = MockEvent(u"%run aa")
116 mockself = None
116 mockself = None
117 match = set(magic_run_completer(mockself, event))
117 match = set(magic_run_completer(mockself, event))
118 self.assertEqual(match, {u"aaø.py"})
118 self.assertEqual(match, {u"aaø.py"})
119
119
120 @onlyif_unicode_paths
120 @onlyif_unicode_paths
121 def test_3(self):
121 def test_3(self):
122 """Test magic_run_completer with unterminated " """
122 """Test magic_run_completer with unterminated " """
123 event = MockEvent(u'%run "a')
123 event = MockEvent(u'%run "a')
124 mockself = None
124 mockself = None
125 match = set(magic_run_completer(mockself, event))
125 match = set(magic_run_completer(mockself, event))
126 self.assertEqual(match, {u"a.py", u"aaø.py"})
126 self.assertEqual(match, {u"a.py", u"aaø.py"})
127
127
128 # module_completer:
128 # module_completer:
129
129
130 def test_import_invalid_module():
130 def test_import_invalid_module():
131 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
131 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
132 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
132 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
133 valid_module_names = {'foobar'}
133 valid_module_names = {'foobar'}
134 with TemporaryDirectory() as tmpdir:
134 with TemporaryDirectory() as tmpdir:
135 sys.path.insert( 0, tmpdir )
135 sys.path.insert( 0, tmpdir )
136 for name in invalid_module_names | valid_module_names:
136 for name in invalid_module_names | valid_module_names:
137 filename = os.path.join(tmpdir, name + ".py")
137 filename = os.path.join(tmpdir, name + ".py")
138 open(filename, "w", encoding="utf-8").close()
138 open(filename, "w", encoding="utf-8").close()
139
139
140 s = set( module_completion('import foo') )
140 s = set( module_completion('import foo') )
141 intersection = s.intersection(invalid_module_names)
141 intersection = s.intersection(invalid_module_names)
142 assert intersection == set()
142 assert intersection == set()
143
143
144 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
144 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
145
145
146
146
147 def test_bad_module_all():
147 def test_bad_module_all():
148 """Test module with invalid __all__
148 """Test module with invalid __all__
149
149
150 https://github.com/ipython/ipython/issues/9678
150 https://github.com/ipython/ipython/issues/9678
151 """
151 """
152 testsdir = os.path.dirname(__file__)
152 testsdir = os.path.dirname(__file__)
153 sys.path.insert(0, testsdir)
153 sys.path.insert(0, testsdir)
154 try:
154 try:
155 results = module_completion("from bad_all import ")
155 results = module_completion("from bad_all import ")
156 assert "puppies" in results
156 assert "puppies" in results
157 for r in results:
157 for r in results:
158 assert isinstance(r, str)
158 assert isinstance(r, str)
159
159
160 # bad_all doesn't contain submodules, but this completion
160 # bad_all doesn't contain submodules, but this completion
161 # should finish without raising an exception:
161 # should finish without raising an exception:
162 results = module_completion("import bad_all.")
162 results = module_completion("import bad_all.")
163 assert results == []
163 assert results == []
164 finally:
164 finally:
165 sys.path.remove(testsdir)
165 sys.path.remove(testsdir)
166
166
167
167
168 def test_module_without_init():
168 def test_module_without_init():
169 """
169 """
170 Test module without __init__.py.
170 Test module without __init__.py.
171
171
172 https://github.com/ipython/ipython/issues/11226
172 https://github.com/ipython/ipython/issues/11226
173 """
173 """
174 fake_module_name = "foo"
174 fake_module_name = "foo"
175 with TemporaryDirectory() as tmpdir:
175 with TemporaryDirectory() as tmpdir:
176 sys.path.insert(0, tmpdir)
176 sys.path.insert(0, tmpdir)
177 try:
177 try:
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
178 os.makedirs(os.path.join(tmpdir, fake_module_name))
179 s = try_import(mod=fake_module_name)
179 s = try_import(mod=fake_module_name)
180 assert s == []
180 assert s == [], f"for module {fake_module_name}"
181 finally:
181 finally:
182 sys.path.remove(tmpdir)
182 sys.path.remove(tmpdir)
183
183
184
184
185 def test_valid_exported_submodules():
185 def test_valid_exported_submodules():
186 """
186 """
187 Test checking exported (__all__) objects are submodules
187 Test checking exported (__all__) objects are submodules
188 """
188 """
189 results = module_completion("import os.pa")
189 results = module_completion("import os.pa")
190 # ensure we get a valid submodule:
190 # ensure we get a valid submodule:
191 assert "os.path" in results
191 assert "os.path" in results
192 # ensure we don't get objects that aren't submodules:
192 # ensure we don't get objects that aren't submodules:
193 assert "os.pathconf" not in results
193 assert "os.pathconf" not in results
@@ -1,1175 +1,1200 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 import pytest
20 import pytest
21 from unittest import mock
21 from unittest import mock
22
22
23 from os.path import join
23 from os.path import join
24
24
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 )
31 )
31 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
32 from IPython.utils.process import find_cmd
33 from IPython.utils.process import find_cmd
33
34
34 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
35 # Globals
36 # Globals
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # This is used by every single test, no point repeating it ad nauseam
38 # This is used by every single test, no point repeating it ad nauseam
38
39
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40 # Tests
41 # Tests
41 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
42
43
43 class DerivedInterrupt(KeyboardInterrupt):
44 class DerivedInterrupt(KeyboardInterrupt):
44 pass
45 pass
45
46
46 class InteractiveShellTestCase(unittest.TestCase):
47 class InteractiveShellTestCase(unittest.TestCase):
47 def test_naked_string_cells(self):
48 def test_naked_string_cells(self):
48 """Test that cells with only naked strings are fully executed"""
49 """Test that cells with only naked strings are fully executed"""
49 # First, single-line inputs
50 # First, single-line inputs
50 ip.run_cell('"a"\n')
51 ip.run_cell('"a"\n')
51 self.assertEqual(ip.user_ns['_'], 'a')
52 self.assertEqual(ip.user_ns['_'], 'a')
52 # And also multi-line cells
53 # And also multi-line cells
53 ip.run_cell('"""a\nb"""\n')
54 ip.run_cell('"""a\nb"""\n')
54 self.assertEqual(ip.user_ns['_'], 'a\nb')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
55
56
56 def test_run_empty_cell(self):
57 def test_run_empty_cell(self):
57 """Just make sure we don't get a horrible error with a blank
58 """Just make sure we don't get a horrible error with a blank
58 cell of input. Yes, I did overlook that."""
59 cell of input. Yes, I did overlook that."""
59 old_xc = ip.execution_count
60 old_xc = ip.execution_count
60 res = ip.run_cell('')
61 res = ip.run_cell('')
61 self.assertEqual(ip.execution_count, old_xc)
62 self.assertEqual(ip.execution_count, old_xc)
62 self.assertEqual(res.execution_count, None)
63 self.assertEqual(res.execution_count, None)
63
64
64 def test_run_cell_multiline(self):
65 def test_run_cell_multiline(self):
65 """Multi-block, multi-line cells must execute correctly.
66 """Multi-block, multi-line cells must execute correctly.
66 """
67 """
67 src = '\n'.join(["x=1",
68 src = '\n'.join(["x=1",
68 "y=2",
69 "y=2",
69 "if 1:",
70 "if 1:",
70 " x += 1",
71 " x += 1",
71 " y += 1",])
72 " y += 1",])
72 res = ip.run_cell(src)
73 res = ip.run_cell(src)
73 self.assertEqual(ip.user_ns['x'], 2)
74 self.assertEqual(ip.user_ns['x'], 2)
74 self.assertEqual(ip.user_ns['y'], 3)
75 self.assertEqual(ip.user_ns['y'], 3)
75 self.assertEqual(res.success, True)
76 self.assertEqual(res.success, True)
76 self.assertEqual(res.result, None)
77 self.assertEqual(res.result, None)
77
78
78 def test_multiline_string_cells(self):
79 def test_multiline_string_cells(self):
79 "Code sprinkled with multiline strings should execute (GH-306)"
80 "Code sprinkled with multiline strings should execute (GH-306)"
80 ip.run_cell('tmp=0')
81 ip.run_cell('tmp=0')
81 self.assertEqual(ip.user_ns['tmp'], 0)
82 self.assertEqual(ip.user_ns['tmp'], 0)
82 res = ip.run_cell('tmp=1;"""a\nb"""\n')
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
83 self.assertEqual(ip.user_ns['tmp'], 1)
84 self.assertEqual(ip.user_ns['tmp'], 1)
84 self.assertEqual(res.success, True)
85 self.assertEqual(res.success, True)
85 self.assertEqual(res.result, "a\nb")
86 self.assertEqual(res.result, "a\nb")
86
87
87 def test_dont_cache_with_semicolon(self):
88 def test_dont_cache_with_semicolon(self):
88 "Ending a line with semicolon should not cache the returned object (GH-307)"
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
89 oldlen = len(ip.user_ns['Out'])
90 oldlen = len(ip.user_ns['Out'])
90 for cell in ['1;', '1;1;']:
91 for cell in ['1;', '1;1;']:
91 res = ip.run_cell(cell, store_history=True)
92 res = ip.run_cell(cell, store_history=True)
92 newlen = len(ip.user_ns['Out'])
93 newlen = len(ip.user_ns['Out'])
93 self.assertEqual(oldlen, newlen)
94 self.assertEqual(oldlen, newlen)
94 self.assertIsNone(res.result)
95 self.assertIsNone(res.result)
95 i = 0
96 i = 0
96 #also test the default caching behavior
97 #also test the default caching behavior
97 for cell in ['1', '1;1']:
98 for cell in ['1', '1;1']:
98 ip.run_cell(cell, store_history=True)
99 ip.run_cell(cell, store_history=True)
99 newlen = len(ip.user_ns['Out'])
100 newlen = len(ip.user_ns['Out'])
100 i += 1
101 i += 1
101 self.assertEqual(oldlen+i, newlen)
102 self.assertEqual(oldlen+i, newlen)
102
103
103 def test_syntax_error(self):
104 def test_syntax_error(self):
104 res = ip.run_cell("raise = 3")
105 res = ip.run_cell("raise = 3")
105 self.assertIsInstance(res.error_before_exec, SyntaxError)
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
106
107
107 def test_open_standard_input_stream(self):
108 def test_open_standard_input_stream(self):
108 res = ip.run_cell("open(0)")
109 res = ip.run_cell("open(0)")
109 self.assertIsInstance(res.error_in_exec, ValueError)
110 self.assertIsInstance(res.error_in_exec, ValueError)
110
111
111 def test_open_standard_output_stream(self):
112 def test_open_standard_output_stream(self):
112 res = ip.run_cell("open(1)")
113 res = ip.run_cell("open(1)")
113 self.assertIsInstance(res.error_in_exec, ValueError)
114 self.assertIsInstance(res.error_in_exec, ValueError)
114
115
115 def test_open_standard_error_stream(self):
116 def test_open_standard_error_stream(self):
116 res = ip.run_cell("open(2)")
117 res = ip.run_cell("open(2)")
117 self.assertIsInstance(res.error_in_exec, ValueError)
118 self.assertIsInstance(res.error_in_exec, ValueError)
118
119
119 def test_In_variable(self):
120 def test_In_variable(self):
120 "Verify that In variable grows with user input (GH-284)"
121 "Verify that In variable grows with user input (GH-284)"
121 oldlen = len(ip.user_ns['In'])
122 oldlen = len(ip.user_ns['In'])
122 ip.run_cell('1;', store_history=True)
123 ip.run_cell('1;', store_history=True)
123 newlen = len(ip.user_ns['In'])
124 newlen = len(ip.user_ns['In'])
124 self.assertEqual(oldlen+1, newlen)
125 self.assertEqual(oldlen+1, newlen)
125 self.assertEqual(ip.user_ns['In'][-1],'1;')
126 self.assertEqual(ip.user_ns['In'][-1],'1;')
126
127
127 def test_magic_names_in_string(self):
128 def test_magic_names_in_string(self):
128 ip.run_cell('a = """\n%exit\n"""')
129 ip.run_cell('a = """\n%exit\n"""')
129 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
130
131
131 def test_trailing_newline(self):
132 def test_trailing_newline(self):
132 """test that running !(command) does not raise a SyntaxError"""
133 """test that running !(command) does not raise a SyntaxError"""
133 ip.run_cell('!(true)\n', False)
134 ip.run_cell('!(true)\n', False)
134 ip.run_cell('!(true)\n\n\n', False)
135 ip.run_cell('!(true)\n\n\n', False)
135
136
136 def test_gh_597(self):
137 def test_gh_597(self):
137 """Pretty-printing lists of objects with non-ascii reprs may cause
138 """Pretty-printing lists of objects with non-ascii reprs may cause
138 problems."""
139 problems."""
139 class Spam(object):
140 class Spam(object):
140 def __repr__(self):
141 def __repr__(self):
141 return "\xe9"*50
142 return "\xe9"*50
142 import IPython.core.formatters
143 import IPython.core.formatters
143 f = IPython.core.formatters.PlainTextFormatter()
144 f = IPython.core.formatters.PlainTextFormatter()
144 f([Spam(),Spam()])
145 f([Spam(),Spam()])
145
146
146
147
147 def test_future_flags(self):
148 def test_future_flags(self):
148 """Check that future flags are used for parsing code (gh-777)"""
149 """Check that future flags are used for parsing code (gh-777)"""
149 ip.run_cell('from __future__ import barry_as_FLUFL')
150 ip.run_cell('from __future__ import barry_as_FLUFL')
150 try:
151 try:
151 ip.run_cell('prfunc_return_val = 1 <> 2')
152 ip.run_cell('prfunc_return_val = 1 <> 2')
152 assert 'prfunc_return_val' in ip.user_ns
153 assert 'prfunc_return_val' in ip.user_ns
153 finally:
154 finally:
154 # Reset compiler flags so we don't mess up other tests.
155 # Reset compiler flags so we don't mess up other tests.
155 ip.compile.reset_compiler_flags()
156 ip.compile.reset_compiler_flags()
156
157
157 def test_can_pickle(self):
158 def test_can_pickle(self):
158 "Can we pickle objects defined interactively (GH-29)"
159 "Can we pickle objects defined interactively (GH-29)"
159 ip = get_ipython()
160 ip = get_ipython()
160 ip.reset()
161 ip.reset()
161 ip.run_cell(("class Mylist(list):\n"
162 ip.run_cell(("class Mylist(list):\n"
162 " def __init__(self,x=[]):\n"
163 " def __init__(self,x=[]):\n"
163 " list.__init__(self,x)"))
164 " list.__init__(self,x)"))
164 ip.run_cell("w=Mylist([1,2,3])")
165 ip.run_cell("w=Mylist([1,2,3])")
165
166
166 from pickle import dumps
167 from pickle import dumps
167
168
168 # We need to swap in our main module - this is only necessary
169 # We need to swap in our main module - this is only necessary
169 # inside the test framework, because IPython puts the interactive module
170 # inside the test framework, because IPython puts the interactive module
170 # in place (but the test framework undoes this).
171 # in place (but the test framework undoes this).
171 _main = sys.modules['__main__']
172 _main = sys.modules['__main__']
172 sys.modules['__main__'] = ip.user_module
173 sys.modules['__main__'] = ip.user_module
173 try:
174 try:
174 res = dumps(ip.user_ns["w"])
175 res = dumps(ip.user_ns["w"])
175 finally:
176 finally:
176 sys.modules['__main__'] = _main
177 sys.modules['__main__'] = _main
177 self.assertTrue(isinstance(res, bytes))
178 self.assertTrue(isinstance(res, bytes))
178
179
179 def test_global_ns(self):
180 def test_global_ns(self):
180 "Code in functions must be able to access variables outside them."
181 "Code in functions must be able to access variables outside them."
181 ip = get_ipython()
182 ip = get_ipython()
182 ip.run_cell("a = 10")
183 ip.run_cell("a = 10")
183 ip.run_cell(("def f(x):\n"
184 ip.run_cell(("def f(x):\n"
184 " return x + a"))
185 " return x + a"))
185 ip.run_cell("b = f(12)")
186 ip.run_cell("b = f(12)")
186 self.assertEqual(ip.user_ns["b"], 22)
187 self.assertEqual(ip.user_ns["b"], 22)
187
188
188 def test_bad_custom_tb(self):
189 def test_bad_custom_tb(self):
189 """Check that InteractiveShell is protected from bad custom exception handlers"""
190 """Check that InteractiveShell is protected from bad custom exception handlers"""
190 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
191 self.assertEqual(ip.custom_exceptions, (IOError,))
192 self.assertEqual(ip.custom_exceptions, (IOError,))
192 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
193 ip.run_cell(u'raise IOError("foo")')
194 ip.run_cell(u'raise IOError("foo")')
194 self.assertEqual(ip.custom_exceptions, ())
195 self.assertEqual(ip.custom_exceptions, ())
195
196
196 def test_bad_custom_tb_return(self):
197 def test_bad_custom_tb_return(self):
197 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
198 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
199 self.assertEqual(ip.custom_exceptions, (NameError,))
200 self.assertEqual(ip.custom_exceptions, (NameError,))
200 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
201 ip.run_cell(u'a=abracadabra')
202 ip.run_cell(u'a=abracadabra')
202 self.assertEqual(ip.custom_exceptions, ())
203 self.assertEqual(ip.custom_exceptions, ())
203
204
204 def test_drop_by_id(self):
205 def test_drop_by_id(self):
205 myvars = {"a":object(), "b":object(), "c": object()}
206 myvars = {"a":object(), "b":object(), "c": object()}
206 ip.push(myvars, interactive=False)
207 ip.push(myvars, interactive=False)
207 for name in myvars:
208 for name in myvars:
208 assert name in ip.user_ns, name
209 assert name in ip.user_ns, name
209 assert name in ip.user_ns_hidden, name
210 assert name in ip.user_ns_hidden, name
210 ip.user_ns['b'] = 12
211 ip.user_ns['b'] = 12
211 ip.drop_by_id(myvars)
212 ip.drop_by_id(myvars)
212 for name in ["a", "c"]:
213 for name in ["a", "c"]:
213 assert name not in ip.user_ns, name
214 assert name not in ip.user_ns, name
214 assert name not in ip.user_ns_hidden, name
215 assert name not in ip.user_ns_hidden, name
215 assert ip.user_ns['b'] == 12
216 assert ip.user_ns['b'] == 12
216 ip.reset()
217 ip.reset()
217
218
218 def test_var_expand(self):
219 def test_var_expand(self):
219 ip.user_ns['f'] = u'Ca\xf1o'
220 ip.user_ns['f'] = u'Ca\xf1o'
220 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
221 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
222 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
223 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
224
225
225 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
226
227
227 ip.user_ns['f'] = b'Ca\xc3\xb1o'
228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
228 # This should not raise any exception:
229 # This should not raise any exception:
229 ip.var_expand(u'echo $f')
230 ip.var_expand(u'echo $f')
230
231
231 def test_var_expand_local(self):
232 def test_var_expand_local(self):
232 """Test local variable expansion in !system and %magic calls"""
233 """Test local variable expansion in !system and %magic calls"""
233 # !system
234 # !system
234 ip.run_cell(
235 ip.run_cell(
235 "def test():\n"
236 "def test():\n"
236 ' lvar = "ttt"\n'
237 ' lvar = "ttt"\n'
237 " ret = !echo {lvar}\n"
238 " ret = !echo {lvar}\n"
238 " return ret[0]\n"
239 " return ret[0]\n"
239 )
240 )
240 res = ip.user_ns["test"]()
241 res = ip.user_ns["test"]()
241 self.assertIn("ttt", res)
242 self.assertIn("ttt", res)
242
243
243 # %magic
244 # %magic
244 ip.run_cell(
245 ip.run_cell(
245 "def makemacro():\n"
246 "def makemacro():\n"
246 ' macroname = "macro_var_expand_locals"\n'
247 ' macroname = "macro_var_expand_locals"\n'
247 " %macro {macroname} codestr\n"
248 " %macro {macroname} codestr\n"
248 )
249 )
249 ip.user_ns["codestr"] = "str(12)"
250 ip.user_ns["codestr"] = "str(12)"
250 ip.run_cell("makemacro()")
251 ip.run_cell("makemacro()")
251 self.assertIn("macro_var_expand_locals", ip.user_ns)
252 self.assertIn("macro_var_expand_locals", ip.user_ns)
252
253
253 def test_var_expand_self(self):
254 def test_var_expand_self(self):
254 """Test variable expansion with the name 'self', which was failing.
255 """Test variable expansion with the name 'self', which was failing.
255
256
256 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
257 """
258 """
258 ip.run_cell(
259 ip.run_cell(
259 "class cTest:\n"
260 "class cTest:\n"
260 ' classvar="see me"\n'
261 ' classvar="see me"\n'
261 " def test(self):\n"
262 " def test(self):\n"
262 " res = !echo Variable: {self.classvar}\n"
263 " res = !echo Variable: {self.classvar}\n"
263 " return res[0]\n"
264 " return res[0]\n"
264 )
265 )
265 self.assertIn("see me", ip.user_ns["cTest"]().test())
266 self.assertIn("see me", ip.user_ns["cTest"]().test())
266
267
267 def test_bad_var_expand(self):
268 def test_bad_var_expand(self):
268 """var_expand on invalid formats shouldn't raise"""
269 """var_expand on invalid formats shouldn't raise"""
269 # SyntaxError
270 # SyntaxError
270 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
271 # NameError
272 # NameError
272 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
273 # ZeroDivisionError
274 # ZeroDivisionError
274 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
275
276
276 def test_silent_postexec(self):
277 def test_silent_postexec(self):
277 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
278 pre_explicit = mock.Mock()
279 pre_explicit = mock.Mock()
279 pre_always = mock.Mock()
280 pre_always = mock.Mock()
280 post_explicit = mock.Mock()
281 post_explicit = mock.Mock()
281 post_always = mock.Mock()
282 post_always = mock.Mock()
282 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
283
284
284 ip.events.register('pre_run_cell', pre_explicit)
285 ip.events.register('pre_run_cell', pre_explicit)
285 ip.events.register('pre_execute', pre_always)
286 ip.events.register('pre_execute', pre_always)
286 ip.events.register('post_run_cell', post_explicit)
287 ip.events.register('post_run_cell', post_explicit)
287 ip.events.register('post_execute', post_always)
288 ip.events.register('post_execute', post_always)
288
289
289 try:
290 try:
290 ip.run_cell("1", silent=True)
291 ip.run_cell("1", silent=True)
291 assert pre_always.called
292 assert pre_always.called
292 assert not pre_explicit.called
293 assert not pre_explicit.called
293 assert post_always.called
294 assert post_always.called
294 assert not post_explicit.called
295 assert not post_explicit.called
295 # double-check that non-silent exec did what we expected
296 # double-check that non-silent exec did what we expected
296 # silent to avoid
297 # silent to avoid
297 ip.run_cell("1")
298 ip.run_cell("1")
298 assert pre_explicit.called
299 assert pre_explicit.called
299 assert post_explicit.called
300 assert post_explicit.called
300 info, = pre_explicit.call_args[0]
301 info, = pre_explicit.call_args[0]
301 result, = post_explicit.call_args[0]
302 result, = post_explicit.call_args[0]
302 self.assertEqual(info, result.info)
303 self.assertEqual(info, result.info)
303 # check that post hooks are always called
304 # check that post hooks are always called
304 [m.reset_mock() for m in all_mocks]
305 [m.reset_mock() for m in all_mocks]
305 ip.run_cell("syntax error")
306 ip.run_cell("syntax error")
306 assert pre_always.called
307 assert pre_always.called
307 assert pre_explicit.called
308 assert pre_explicit.called
308 assert post_always.called
309 assert post_always.called
309 assert post_explicit.called
310 assert post_explicit.called
310 info, = pre_explicit.call_args[0]
311 info, = pre_explicit.call_args[0]
311 result, = post_explicit.call_args[0]
312 result, = post_explicit.call_args[0]
312 self.assertEqual(info, result.info)
313 self.assertEqual(info, result.info)
313 finally:
314 finally:
314 # remove post-exec
315 # remove post-exec
315 ip.events.unregister('pre_run_cell', pre_explicit)
316 ip.events.unregister('pre_run_cell', pre_explicit)
316 ip.events.unregister('pre_execute', pre_always)
317 ip.events.unregister('pre_execute', pre_always)
317 ip.events.unregister('post_run_cell', post_explicit)
318 ip.events.unregister('post_run_cell', post_explicit)
318 ip.events.unregister('post_execute', post_always)
319 ip.events.unregister('post_execute', post_always)
319
320
320 def test_silent_noadvance(self):
321 def test_silent_noadvance(self):
321 """run_cell(silent=True) doesn't advance execution_count"""
322 """run_cell(silent=True) doesn't advance execution_count"""
322 ec = ip.execution_count
323 ec = ip.execution_count
323 # silent should force store_history=False
324 # silent should force store_history=False
324 ip.run_cell("1", store_history=True, silent=True)
325 ip.run_cell("1", store_history=True, silent=True)
325
326
326 self.assertEqual(ec, ip.execution_count)
327 self.assertEqual(ec, ip.execution_count)
327 # double-check that non-silent exec did what we expected
328 # double-check that non-silent exec did what we expected
328 # silent to avoid
329 # silent to avoid
329 ip.run_cell("1", store_history=True)
330 ip.run_cell("1", store_history=True)
330 self.assertEqual(ec+1, ip.execution_count)
331 self.assertEqual(ec+1, ip.execution_count)
331
332
332 def test_silent_nodisplayhook(self):
333 def test_silent_nodisplayhook(self):
333 """run_cell(silent=True) doesn't trigger displayhook"""
334 """run_cell(silent=True) doesn't trigger displayhook"""
334 d = dict(called=False)
335 d = dict(called=False)
335
336
336 trap = ip.display_trap
337 trap = ip.display_trap
337 save_hook = trap.hook
338 save_hook = trap.hook
338
339
339 def failing_hook(*args, **kwargs):
340 def failing_hook(*args, **kwargs):
340 d['called'] = True
341 d['called'] = True
341
342
342 try:
343 try:
343 trap.hook = failing_hook
344 trap.hook = failing_hook
344 res = ip.run_cell("1", silent=True)
345 res = ip.run_cell("1", silent=True)
345 self.assertFalse(d['called'])
346 self.assertFalse(d['called'])
346 self.assertIsNone(res.result)
347 self.assertIsNone(res.result)
347 # double-check that non-silent exec did what we expected
348 # double-check that non-silent exec did what we expected
348 # silent to avoid
349 # silent to avoid
349 ip.run_cell("1")
350 ip.run_cell("1")
350 self.assertTrue(d['called'])
351 self.assertTrue(d['called'])
351 finally:
352 finally:
352 trap.hook = save_hook
353 trap.hook = save_hook
353
354
354 def test_ofind_line_magic(self):
355 def test_ofind_line_magic(self):
355 from IPython.core.magic import register_line_magic
356 from IPython.core.magic import register_line_magic
356
357
357 @register_line_magic
358 @register_line_magic
358 def lmagic(line):
359 def lmagic(line):
359 "A line magic"
360 "A line magic"
360
361
361 # Get info on line magic
362 # Get info on line magic
362 lfind = ip._ofind("lmagic")
363 lfind = ip._ofind("lmagic")
363 info = dict(
364 info = OInfo(
364 found=True,
365 found=True,
365 isalias=False,
366 isalias=False,
366 ismagic=True,
367 ismagic=True,
367 namespace="IPython internal",
368 namespace="IPython internal",
368 obj=lmagic,
369 obj=lmagic,
369 parent=None,
370 parent=None,
370 )
371 )
371 self.assertEqual(lfind, info)
372 self.assertEqual(lfind, info)
372
373
373 def test_ofind_cell_magic(self):
374 def test_ofind_cell_magic(self):
374 from IPython.core.magic import register_cell_magic
375 from IPython.core.magic import register_cell_magic
375
376
376 @register_cell_magic
377 @register_cell_magic
377 def cmagic(line, cell):
378 def cmagic(line, cell):
378 "A cell magic"
379 "A cell magic"
379
380
380 # Get info on cell magic
381 # Get info on cell magic
381 find = ip._ofind("cmagic")
382 find = ip._ofind("cmagic")
382 info = dict(
383 info = OInfo(
383 found=True,
384 found=True,
384 isalias=False,
385 isalias=False,
385 ismagic=True,
386 ismagic=True,
386 namespace="IPython internal",
387 namespace="IPython internal",
387 obj=cmagic,
388 obj=cmagic,
388 parent=None,
389 parent=None,
389 )
390 )
390 self.assertEqual(find, info)
391 self.assertEqual(find, info)
391
392
392 def test_ofind_property_with_error(self):
393 def test_ofind_property_with_error(self):
393 class A(object):
394 class A(object):
394 @property
395 @property
395 def foo(self):
396 def foo(self):
396 raise NotImplementedError() # pragma: no cover
397 raise NotImplementedError() # pragma: no cover
397
398
398 a = A()
399 a = A()
399
400
400 found = ip._ofind('a.foo', [('locals', locals())])
401 found = ip._ofind("a.foo", [("locals", locals())])
401 info = dict(found=True, isalias=False, ismagic=False,
402 info = OInfo(
402 namespace='locals', obj=A.foo, parent=a)
403 found=True,
404 isalias=False,
405 ismagic=False,
406 namespace="locals",
407 obj=A.foo,
408 parent=a,
409 )
403 self.assertEqual(found, info)
410 self.assertEqual(found, info)
404
411
405 def test_ofind_multiple_attribute_lookups(self):
412 def test_ofind_multiple_attribute_lookups(self):
406 class A(object):
413 class A(object):
407 @property
414 @property
408 def foo(self):
415 def foo(self):
409 raise NotImplementedError() # pragma: no cover
416 raise NotImplementedError() # pragma: no cover
410
417
411 a = A()
418 a = A()
412 a.a = A()
419 a.a = A()
413 a.a.a = A()
420 a.a.a = A()
414
421
415 found = ip._ofind('a.a.a.foo', [('locals', locals())])
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
416 info = dict(found=True, isalias=False, ismagic=False,
423 info = OInfo(
417 namespace='locals', obj=A.foo, parent=a.a.a)
424 found=True,
425 isalias=False,
426 ismagic=False,
427 namespace="locals",
428 obj=A.foo,
429 parent=a.a.a,
430 )
418 self.assertEqual(found, info)
431 self.assertEqual(found, info)
419
432
420 def test_ofind_slotted_attributes(self):
433 def test_ofind_slotted_attributes(self):
421 class A(object):
434 class A(object):
422 __slots__ = ['foo']
435 __slots__ = ['foo']
423 def __init__(self):
436 def __init__(self):
424 self.foo = 'bar'
437 self.foo = 'bar'
425
438
426 a = A()
439 a = A()
427 found = ip._ofind('a.foo', [('locals', locals())])
440 found = ip._ofind("a.foo", [("locals", locals())])
428 info = dict(found=True, isalias=False, ismagic=False,
441 info = OInfo(
429 namespace='locals', obj=a.foo, parent=a)
442 found=True,
443 isalias=False,
444 ismagic=False,
445 namespace="locals",
446 obj=a.foo,
447 parent=a,
448 )
430 self.assertEqual(found, info)
449 self.assertEqual(found, info)
431
450
432 found = ip._ofind('a.bar', [('locals', locals())])
451 found = ip._ofind("a.bar", [("locals", locals())])
433 info = dict(found=False, isalias=False, ismagic=False,
452 info = OInfo(
434 namespace=None, obj=None, parent=a)
453 found=False,
454 isalias=False,
455 ismagic=False,
456 namespace=None,
457 obj=None,
458 parent=a,
459 )
435 self.assertEqual(found, info)
460 self.assertEqual(found, info)
436
461
437 def test_ofind_prefers_property_to_instance_level_attribute(self):
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
438 class A(object):
463 class A(object):
439 @property
464 @property
440 def foo(self):
465 def foo(self):
441 return 'bar'
466 return 'bar'
442 a = A()
467 a = A()
443 a.__dict__["foo"] = "baz"
468 a.__dict__["foo"] = "baz"
444 self.assertEqual(a.foo, "bar")
469 self.assertEqual(a.foo, "bar")
445 found = ip._ofind("a.foo", [("locals", locals())])
470 found = ip._ofind("a.foo", [("locals", locals())])
446 self.assertIs(found["obj"], A.foo)
471 self.assertIs(found.obj, A.foo)
447
472
448 def test_custom_syntaxerror_exception(self):
473 def test_custom_syntaxerror_exception(self):
449 called = []
474 called = []
450 def my_handler(shell, etype, value, tb, tb_offset=None):
475 def my_handler(shell, etype, value, tb, tb_offset=None):
451 called.append(etype)
476 called.append(etype)
452 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
453
478
454 ip.set_custom_exc((SyntaxError,), my_handler)
479 ip.set_custom_exc((SyntaxError,), my_handler)
455 try:
480 try:
456 ip.run_cell("1f")
481 ip.run_cell("1f")
457 # Check that this was called, and only once.
482 # Check that this was called, and only once.
458 self.assertEqual(called, [SyntaxError])
483 self.assertEqual(called, [SyntaxError])
459 finally:
484 finally:
460 # Reset the custom exception hook
485 # Reset the custom exception hook
461 ip.set_custom_exc((), None)
486 ip.set_custom_exc((), None)
462
487
463 def test_custom_exception(self):
488 def test_custom_exception(self):
464 called = []
489 called = []
465 def my_handler(shell, etype, value, tb, tb_offset=None):
490 def my_handler(shell, etype, value, tb, tb_offset=None):
466 called.append(etype)
491 called.append(etype)
467 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
468
493
469 ip.set_custom_exc((ValueError,), my_handler)
494 ip.set_custom_exc((ValueError,), my_handler)
470 try:
495 try:
471 res = ip.run_cell("raise ValueError('test')")
496 res = ip.run_cell("raise ValueError('test')")
472 # Check that this was called, and only once.
497 # Check that this was called, and only once.
473 self.assertEqual(called, [ValueError])
498 self.assertEqual(called, [ValueError])
474 # Check that the error is on the result object
499 # Check that the error is on the result object
475 self.assertIsInstance(res.error_in_exec, ValueError)
500 self.assertIsInstance(res.error_in_exec, ValueError)
476 finally:
501 finally:
477 # Reset the custom exception hook
502 # Reset the custom exception hook
478 ip.set_custom_exc((), None)
503 ip.set_custom_exc((), None)
479
504
480 @mock.patch("builtins.print")
505 @mock.patch("builtins.print")
481 def test_showtraceback_with_surrogates(self, mocked_print):
506 def test_showtraceback_with_surrogates(self, mocked_print):
482 values = []
507 values = []
483
508
484 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
485 values.append(value)
510 values.append(value)
486 if value == chr(0xD8FF):
511 if value == chr(0xD8FF):
487 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
488
513
489 # mock builtins.print
514 # mock builtins.print
490 mocked_print.side_effect = mock_print_func
515 mocked_print.side_effect = mock_print_func
491
516
492 # ip._showtraceback() is replaced in globalipapp.py.
517 # ip._showtraceback() is replaced in globalipapp.py.
493 # Call original method to test.
518 # Call original method to test.
494 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
495
520
496 self.assertEqual(mocked_print.call_count, 2)
521 self.assertEqual(mocked_print.call_count, 2)
497 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
498
523
499 def test_mktempfile(self):
524 def test_mktempfile(self):
500 filename = ip.mktempfile()
525 filename = ip.mktempfile()
501 # Check that we can open the file again on Windows
526 # Check that we can open the file again on Windows
502 with open(filename, "w", encoding="utf-8") as f:
527 with open(filename, "w", encoding="utf-8") as f:
503 f.write("abc")
528 f.write("abc")
504
529
505 filename = ip.mktempfile(data="blah")
530 filename = ip.mktempfile(data="blah")
506 with open(filename, "r", encoding="utf-8") as f:
531 with open(filename, "r", encoding="utf-8") as f:
507 self.assertEqual(f.read(), "blah")
532 self.assertEqual(f.read(), "blah")
508
533
509 def test_new_main_mod(self):
534 def test_new_main_mod(self):
510 # Smoketest to check that this accepts a unicode module name
535 # Smoketest to check that this accepts a unicode module name
511 name = u'jiefmw'
536 name = u'jiefmw'
512 mod = ip.new_main_mod(u'%s.py' % name, name)
537 mod = ip.new_main_mod(u'%s.py' % name, name)
513 self.assertEqual(mod.__name__, name)
538 self.assertEqual(mod.__name__, name)
514
539
515 def test_get_exception_only(self):
540 def test_get_exception_only(self):
516 try:
541 try:
517 raise KeyboardInterrupt
542 raise KeyboardInterrupt
518 except KeyboardInterrupt:
543 except KeyboardInterrupt:
519 msg = ip.get_exception_only()
544 msg = ip.get_exception_only()
520 self.assertEqual(msg, 'KeyboardInterrupt\n')
545 self.assertEqual(msg, 'KeyboardInterrupt\n')
521
546
522 try:
547 try:
523 raise DerivedInterrupt("foo")
548 raise DerivedInterrupt("foo")
524 except KeyboardInterrupt:
549 except KeyboardInterrupt:
525 msg = ip.get_exception_only()
550 msg = ip.get_exception_only()
526 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
527
552
528 def test_inspect_text(self):
553 def test_inspect_text(self):
529 ip.run_cell('a = 5')
554 ip.run_cell('a = 5')
530 text = ip.object_inspect_text('a')
555 text = ip.object_inspect_text('a')
531 self.assertIsInstance(text, str)
556 self.assertIsInstance(text, str)
532
557
533 def test_last_execution_result(self):
558 def test_last_execution_result(self):
534 """ Check that last execution result gets set correctly (GH-10702) """
559 """ Check that last execution result gets set correctly (GH-10702) """
535 result = ip.run_cell('a = 5; a')
560 result = ip.run_cell('a = 5; a')
536 self.assertTrue(ip.last_execution_succeeded)
561 self.assertTrue(ip.last_execution_succeeded)
537 self.assertEqual(ip.last_execution_result.result, 5)
562 self.assertEqual(ip.last_execution_result.result, 5)
538
563
539 result = ip.run_cell('a = x_invalid_id_x')
564 result = ip.run_cell('a = x_invalid_id_x')
540 self.assertFalse(ip.last_execution_succeeded)
565 self.assertFalse(ip.last_execution_succeeded)
541 self.assertFalse(ip.last_execution_result.success)
566 self.assertFalse(ip.last_execution_result.success)
542 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
543
568
544 def test_reset_aliasing(self):
569 def test_reset_aliasing(self):
545 """ Check that standard posix aliases work after %reset. """
570 """ Check that standard posix aliases work after %reset. """
546 if os.name != 'posix':
571 if os.name != 'posix':
547 return
572 return
548
573
549 ip.reset()
574 ip.reset()
550 for cmd in ('clear', 'more', 'less', 'man'):
575 for cmd in ('clear', 'more', 'less', 'man'):
551 res = ip.run_cell('%' + cmd)
576 res = ip.run_cell('%' + cmd)
552 self.assertEqual(res.success, True)
577 self.assertEqual(res.success, True)
553
578
554
579
555 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
580 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
556
581
557 @onlyif_unicode_paths
582 @onlyif_unicode_paths
558 def setUp(self):
583 def setUp(self):
559 self.BASETESTDIR = tempfile.mkdtemp()
584 self.BASETESTDIR = tempfile.mkdtemp()
560 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
585 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
561 os.mkdir(self.TESTDIR)
586 os.mkdir(self.TESTDIR)
562 with open(
587 with open(
563 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
588 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
564 ) as sfile:
589 ) as sfile:
565 sfile.write("pass\n")
590 sfile.write("pass\n")
566 self.oldpath = os.getcwd()
591 self.oldpath = os.getcwd()
567 os.chdir(self.TESTDIR)
592 os.chdir(self.TESTDIR)
568 self.fname = u"åäötestscript.py"
593 self.fname = u"åäötestscript.py"
569
594
570 def tearDown(self):
595 def tearDown(self):
571 os.chdir(self.oldpath)
596 os.chdir(self.oldpath)
572 shutil.rmtree(self.BASETESTDIR)
597 shutil.rmtree(self.BASETESTDIR)
573
598
574 @onlyif_unicode_paths
599 @onlyif_unicode_paths
575 def test_1(self):
600 def test_1(self):
576 """Test safe_execfile with non-ascii path
601 """Test safe_execfile with non-ascii path
577 """
602 """
578 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
603 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
579
604
580 class ExitCodeChecks(tt.TempFileMixin):
605 class ExitCodeChecks(tt.TempFileMixin):
581
606
582 def setUp(self):
607 def setUp(self):
583 self.system = ip.system_raw
608 self.system = ip.system_raw
584
609
585 def test_exit_code_ok(self):
610 def test_exit_code_ok(self):
586 self.system('exit 0')
611 self.system('exit 0')
587 self.assertEqual(ip.user_ns['_exit_code'], 0)
612 self.assertEqual(ip.user_ns['_exit_code'], 0)
588
613
589 def test_exit_code_error(self):
614 def test_exit_code_error(self):
590 self.system('exit 1')
615 self.system('exit 1')
591 self.assertEqual(ip.user_ns['_exit_code'], 1)
616 self.assertEqual(ip.user_ns['_exit_code'], 1)
592
617
593 @skipif(not hasattr(signal, 'SIGALRM'))
618 @skipif(not hasattr(signal, 'SIGALRM'))
594 def test_exit_code_signal(self):
619 def test_exit_code_signal(self):
595 self.mktmp("import signal, time\n"
620 self.mktmp("import signal, time\n"
596 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
621 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
597 "time.sleep(1)\n")
622 "time.sleep(1)\n")
598 self.system("%s %s" % (sys.executable, self.fname))
623 self.system("%s %s" % (sys.executable, self.fname))
599 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
624 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
600
625
601 @onlyif_cmds_exist("csh")
626 @onlyif_cmds_exist("csh")
602 def test_exit_code_signal_csh(self): # pragma: no cover
627 def test_exit_code_signal_csh(self): # pragma: no cover
603 SHELL = os.environ.get("SHELL", None)
628 SHELL = os.environ.get("SHELL", None)
604 os.environ["SHELL"] = find_cmd("csh")
629 os.environ["SHELL"] = find_cmd("csh")
605 try:
630 try:
606 self.test_exit_code_signal()
631 self.test_exit_code_signal()
607 finally:
632 finally:
608 if SHELL is not None:
633 if SHELL is not None:
609 os.environ['SHELL'] = SHELL
634 os.environ['SHELL'] = SHELL
610 else:
635 else:
611 del os.environ['SHELL']
636 del os.environ['SHELL']
612
637
613
638
614 class TestSystemRaw(ExitCodeChecks):
639 class TestSystemRaw(ExitCodeChecks):
615
640
616 def setUp(self):
641 def setUp(self):
617 super().setUp()
642 super().setUp()
618 self.system = ip.system_raw
643 self.system = ip.system_raw
619
644
620 @onlyif_unicode_paths
645 @onlyif_unicode_paths
621 def test_1(self):
646 def test_1(self):
622 """Test system_raw with non-ascii cmd
647 """Test system_raw with non-ascii cmd
623 """
648 """
624 cmd = u'''python -c "'åäö'" '''
649 cmd = u'''python -c "'åäö'" '''
625 ip.system_raw(cmd)
650 ip.system_raw(cmd)
626
651
627 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
652 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
628 @mock.patch('os.system', side_effect=KeyboardInterrupt)
653 @mock.patch('os.system', side_effect=KeyboardInterrupt)
629 def test_control_c(self, *mocks):
654 def test_control_c(self, *mocks):
630 try:
655 try:
631 self.system("sleep 1 # wont happen")
656 self.system("sleep 1 # wont happen")
632 except KeyboardInterrupt: # pragma: no cove
657 except KeyboardInterrupt: # pragma: no cove
633 self.fail(
658 self.fail(
634 "system call should intercept "
659 "system call should intercept "
635 "keyboard interrupt from subprocess.call"
660 "keyboard interrupt from subprocess.call"
636 )
661 )
637 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
662 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
638
663
639
664
640 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
665 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
641 def test_magic_warnings(magic_cmd):
666 def test_magic_warnings(magic_cmd):
642 if sys.platform == "win32":
667 if sys.platform == "win32":
643 to_mock = "os.system"
668 to_mock = "os.system"
644 expected_arg, expected_kwargs = magic_cmd, dict()
669 expected_arg, expected_kwargs = magic_cmd, dict()
645 else:
670 else:
646 to_mock = "subprocess.call"
671 to_mock = "subprocess.call"
647 expected_arg, expected_kwargs = magic_cmd, dict(
672 expected_arg, expected_kwargs = magic_cmd, dict(
648 shell=True, executable=os.environ.get("SHELL", None)
673 shell=True, executable=os.environ.get("SHELL", None)
649 )
674 )
650
675
651 with mock.patch(to_mock, return_value=0) as mock_sub:
676 with mock.patch(to_mock, return_value=0) as mock_sub:
652 with pytest.warns(Warning, match=r"You executed the system command"):
677 with pytest.warns(Warning, match=r"You executed the system command"):
653 ip.system_raw(magic_cmd)
678 ip.system_raw(magic_cmd)
654 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
679 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
655
680
656
681
657 # TODO: Exit codes are currently ignored on Windows.
682 # TODO: Exit codes are currently ignored on Windows.
658 class TestSystemPipedExitCode(ExitCodeChecks):
683 class TestSystemPipedExitCode(ExitCodeChecks):
659
684
660 def setUp(self):
685 def setUp(self):
661 super().setUp()
686 super().setUp()
662 self.system = ip.system_piped
687 self.system = ip.system_piped
663
688
664 @skip_win32
689 @skip_win32
665 def test_exit_code_ok(self):
690 def test_exit_code_ok(self):
666 ExitCodeChecks.test_exit_code_ok(self)
691 ExitCodeChecks.test_exit_code_ok(self)
667
692
668 @skip_win32
693 @skip_win32
669 def test_exit_code_error(self):
694 def test_exit_code_error(self):
670 ExitCodeChecks.test_exit_code_error(self)
695 ExitCodeChecks.test_exit_code_error(self)
671
696
672 @skip_win32
697 @skip_win32
673 def test_exit_code_signal(self):
698 def test_exit_code_signal(self):
674 ExitCodeChecks.test_exit_code_signal(self)
699 ExitCodeChecks.test_exit_code_signal(self)
675
700
676 class TestModules(tt.TempFileMixin):
701 class TestModules(tt.TempFileMixin):
677 def test_extraneous_loads(self):
702 def test_extraneous_loads(self):
678 """Test we're not loading modules on startup that we shouldn't.
703 """Test we're not loading modules on startup that we shouldn't.
679 """
704 """
680 self.mktmp("import sys\n"
705 self.mktmp("import sys\n"
681 "print('numpy' in sys.modules)\n"
706 "print('numpy' in sys.modules)\n"
682 "print('ipyparallel' in sys.modules)\n"
707 "print('ipyparallel' in sys.modules)\n"
683 "print('ipykernel' in sys.modules)\n"
708 "print('ipykernel' in sys.modules)\n"
684 )
709 )
685 out = "False\nFalse\nFalse\n"
710 out = "False\nFalse\nFalse\n"
686 tt.ipexec_validate(self.fname, out)
711 tt.ipexec_validate(self.fname, out)
687
712
688 class Negator(ast.NodeTransformer):
713 class Negator(ast.NodeTransformer):
689 """Negates all number literals in an AST."""
714 """Negates all number literals in an AST."""
690
715
691 # for python 3.7 and earlier
716 # for python 3.7 and earlier
692 def visit_Num(self, node):
717 def visit_Num(self, node):
693 node.n = -node.n
718 node.n = -node.n
694 return node
719 return node
695
720
696 # for python 3.8+
721 # for python 3.8+
697 def visit_Constant(self, node):
722 def visit_Constant(self, node):
698 if isinstance(node.value, int):
723 if isinstance(node.value, int):
699 return self.visit_Num(node)
724 return self.visit_Num(node)
700 return node
725 return node
701
726
702 class TestAstTransform(unittest.TestCase):
727 class TestAstTransform(unittest.TestCase):
703 def setUp(self):
728 def setUp(self):
704 self.negator = Negator()
729 self.negator = Negator()
705 ip.ast_transformers.append(self.negator)
730 ip.ast_transformers.append(self.negator)
706
731
707 def tearDown(self):
732 def tearDown(self):
708 ip.ast_transformers.remove(self.negator)
733 ip.ast_transformers.remove(self.negator)
709
734
710 def test_non_int_const(self):
735 def test_non_int_const(self):
711 with tt.AssertPrints("hello"):
736 with tt.AssertPrints("hello"):
712 ip.run_cell('print("hello")')
737 ip.run_cell('print("hello")')
713
738
714 def test_run_cell(self):
739 def test_run_cell(self):
715 with tt.AssertPrints("-34"):
740 with tt.AssertPrints("-34"):
716 ip.run_cell("print(12 + 22)")
741 ip.run_cell("print(12 + 22)")
717
742
718 # A named reference to a number shouldn't be transformed.
743 # A named reference to a number shouldn't be transformed.
719 ip.user_ns["n"] = 55
744 ip.user_ns["n"] = 55
720 with tt.AssertNotPrints("-55"):
745 with tt.AssertNotPrints("-55"):
721 ip.run_cell("print(n)")
746 ip.run_cell("print(n)")
722
747
723 def test_timeit(self):
748 def test_timeit(self):
724 called = set()
749 called = set()
725 def f(x):
750 def f(x):
726 called.add(x)
751 called.add(x)
727 ip.push({'f':f})
752 ip.push({'f':f})
728
753
729 with tt.AssertPrints("std. dev. of"):
754 with tt.AssertPrints("std. dev. of"):
730 ip.run_line_magic("timeit", "-n1 f(1)")
755 ip.run_line_magic("timeit", "-n1 f(1)")
731 self.assertEqual(called, {-1})
756 self.assertEqual(called, {-1})
732 called.clear()
757 called.clear()
733
758
734 with tt.AssertPrints("std. dev. of"):
759 with tt.AssertPrints("std. dev. of"):
735 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
760 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
736 self.assertEqual(called, {-2, -3})
761 self.assertEqual(called, {-2, -3})
737
762
738 def test_time(self):
763 def test_time(self):
739 called = []
764 called = []
740 def f(x):
765 def f(x):
741 called.append(x)
766 called.append(x)
742 ip.push({'f':f})
767 ip.push({'f':f})
743
768
744 # Test with an expression
769 # Test with an expression
745 with tt.AssertPrints("Wall time: "):
770 with tt.AssertPrints("Wall time: "):
746 ip.run_line_magic("time", "f(5+9)")
771 ip.run_line_magic("time", "f(5+9)")
747 self.assertEqual(called, [-14])
772 self.assertEqual(called, [-14])
748 called[:] = []
773 called[:] = []
749
774
750 # Test with a statement (different code path)
775 # Test with a statement (different code path)
751 with tt.AssertPrints("Wall time: "):
776 with tt.AssertPrints("Wall time: "):
752 ip.run_line_magic("time", "a = f(-3 + -2)")
777 ip.run_line_magic("time", "a = f(-3 + -2)")
753 self.assertEqual(called, [5])
778 self.assertEqual(called, [5])
754
779
755 def test_macro(self):
780 def test_macro(self):
756 ip.push({'a':10})
781 ip.push({'a':10})
757 # The AST transformation makes this do a+=-1
782 # The AST transformation makes this do a+=-1
758 ip.define_macro("amacro", "a+=1\nprint(a)")
783 ip.define_macro("amacro", "a+=1\nprint(a)")
759
784
760 with tt.AssertPrints("9"):
785 with tt.AssertPrints("9"):
761 ip.run_cell("amacro")
786 ip.run_cell("amacro")
762 with tt.AssertPrints("8"):
787 with tt.AssertPrints("8"):
763 ip.run_cell("amacro")
788 ip.run_cell("amacro")
764
789
765 class TestMiscTransform(unittest.TestCase):
790 class TestMiscTransform(unittest.TestCase):
766
791
767
792
768 def test_transform_only_once(self):
793 def test_transform_only_once(self):
769 cleanup = 0
794 cleanup = 0
770 line_t = 0
795 line_t = 0
771 def count_cleanup(lines):
796 def count_cleanup(lines):
772 nonlocal cleanup
797 nonlocal cleanup
773 cleanup += 1
798 cleanup += 1
774 return lines
799 return lines
775
800
776 def count_line_t(lines):
801 def count_line_t(lines):
777 nonlocal line_t
802 nonlocal line_t
778 line_t += 1
803 line_t += 1
779 return lines
804 return lines
780
805
781 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
806 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
782 ip.input_transformer_manager.line_transforms.append(count_line_t)
807 ip.input_transformer_manager.line_transforms.append(count_line_t)
783
808
784 ip.run_cell('1')
809 ip.run_cell('1')
785
810
786 assert cleanup == 1
811 assert cleanup == 1
787 assert line_t == 1
812 assert line_t == 1
788
813
789 class IntegerWrapper(ast.NodeTransformer):
814 class IntegerWrapper(ast.NodeTransformer):
790 """Wraps all integers in a call to Integer()"""
815 """Wraps all integers in a call to Integer()"""
791
816
792 # for Python 3.7 and earlier
817 # for Python 3.7 and earlier
793
818
794 # for Python 3.7 and earlier
819 # for Python 3.7 and earlier
795 def visit_Num(self, node):
820 def visit_Num(self, node):
796 if isinstance(node.n, int):
821 if isinstance(node.n, int):
797 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
822 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
798 args=[node], keywords=[])
823 args=[node], keywords=[])
799 return node
824 return node
800
825
801 # For Python 3.8+
826 # For Python 3.8+
802 def visit_Constant(self, node):
827 def visit_Constant(self, node):
803 if isinstance(node.value, int):
828 if isinstance(node.value, int):
804 return self.visit_Num(node)
829 return self.visit_Num(node)
805 return node
830 return node
806
831
807
832
808 class TestAstTransform2(unittest.TestCase):
833 class TestAstTransform2(unittest.TestCase):
809 def setUp(self):
834 def setUp(self):
810 self.intwrapper = IntegerWrapper()
835 self.intwrapper = IntegerWrapper()
811 ip.ast_transformers.append(self.intwrapper)
836 ip.ast_transformers.append(self.intwrapper)
812
837
813 self.calls = []
838 self.calls = []
814 def Integer(*args):
839 def Integer(*args):
815 self.calls.append(args)
840 self.calls.append(args)
816 return args
841 return args
817 ip.push({"Integer": Integer})
842 ip.push({"Integer": Integer})
818
843
819 def tearDown(self):
844 def tearDown(self):
820 ip.ast_transformers.remove(self.intwrapper)
845 ip.ast_transformers.remove(self.intwrapper)
821 del ip.user_ns['Integer']
846 del ip.user_ns['Integer']
822
847
823 def test_run_cell(self):
848 def test_run_cell(self):
824 ip.run_cell("n = 2")
849 ip.run_cell("n = 2")
825 self.assertEqual(self.calls, [(2,)])
850 self.assertEqual(self.calls, [(2,)])
826
851
827 # This shouldn't throw an error
852 # This shouldn't throw an error
828 ip.run_cell("o = 2.0")
853 ip.run_cell("o = 2.0")
829 self.assertEqual(ip.user_ns['o'], 2.0)
854 self.assertEqual(ip.user_ns['o'], 2.0)
830
855
831 def test_run_cell_non_int(self):
856 def test_run_cell_non_int(self):
832 ip.run_cell("n = 'a'")
857 ip.run_cell("n = 'a'")
833 assert self.calls == []
858 assert self.calls == []
834
859
835 def test_timeit(self):
860 def test_timeit(self):
836 called = set()
861 called = set()
837 def f(x):
862 def f(x):
838 called.add(x)
863 called.add(x)
839 ip.push({'f':f})
864 ip.push({'f':f})
840
865
841 with tt.AssertPrints("std. dev. of"):
866 with tt.AssertPrints("std. dev. of"):
842 ip.run_line_magic("timeit", "-n1 f(1)")
867 ip.run_line_magic("timeit", "-n1 f(1)")
843 self.assertEqual(called, {(1,)})
868 self.assertEqual(called, {(1,)})
844 called.clear()
869 called.clear()
845
870
846 with tt.AssertPrints("std. dev. of"):
871 with tt.AssertPrints("std. dev. of"):
847 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
872 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
848 self.assertEqual(called, {(2,), (3,)})
873 self.assertEqual(called, {(2,), (3,)})
849
874
850 class ErrorTransformer(ast.NodeTransformer):
875 class ErrorTransformer(ast.NodeTransformer):
851 """Throws an error when it sees a number."""
876 """Throws an error when it sees a number."""
852
877
853 def visit_Constant(self, node):
878 def visit_Constant(self, node):
854 if isinstance(node.value, int):
879 if isinstance(node.value, int):
855 raise ValueError("test")
880 raise ValueError("test")
856 return node
881 return node
857
882
858
883
859 class TestAstTransformError(unittest.TestCase):
884 class TestAstTransformError(unittest.TestCase):
860 def test_unregistering(self):
885 def test_unregistering(self):
861 err_transformer = ErrorTransformer()
886 err_transformer = ErrorTransformer()
862 ip.ast_transformers.append(err_transformer)
887 ip.ast_transformers.append(err_transformer)
863
888
864 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
889 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
865 ip.run_cell("1 + 2")
890 ip.run_cell("1 + 2")
866
891
867 # This should have been removed.
892 # This should have been removed.
868 self.assertNotIn(err_transformer, ip.ast_transformers)
893 self.assertNotIn(err_transformer, ip.ast_transformers)
869
894
870
895
871 class StringRejector(ast.NodeTransformer):
896 class StringRejector(ast.NodeTransformer):
872 """Throws an InputRejected when it sees a string literal.
897 """Throws an InputRejected when it sees a string literal.
873
898
874 Used to verify that NodeTransformers can signal that a piece of code should
899 Used to verify that NodeTransformers can signal that a piece of code should
875 not be executed by throwing an InputRejected.
900 not be executed by throwing an InputRejected.
876 """
901 """
877
902
878 # 3.8 only
903 # 3.8 only
879 def visit_Constant(self, node):
904 def visit_Constant(self, node):
880 if isinstance(node.value, str):
905 if isinstance(node.value, str):
881 raise InputRejected("test")
906 raise InputRejected("test")
882 return node
907 return node
883
908
884
909
885 class TestAstTransformInputRejection(unittest.TestCase):
910 class TestAstTransformInputRejection(unittest.TestCase):
886
911
887 def setUp(self):
912 def setUp(self):
888 self.transformer = StringRejector()
913 self.transformer = StringRejector()
889 ip.ast_transformers.append(self.transformer)
914 ip.ast_transformers.append(self.transformer)
890
915
891 def tearDown(self):
916 def tearDown(self):
892 ip.ast_transformers.remove(self.transformer)
917 ip.ast_transformers.remove(self.transformer)
893
918
894 def test_input_rejection(self):
919 def test_input_rejection(self):
895 """Check that NodeTransformers can reject input."""
920 """Check that NodeTransformers can reject input."""
896
921
897 expect_exception_tb = tt.AssertPrints("InputRejected: test")
922 expect_exception_tb = tt.AssertPrints("InputRejected: test")
898 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
923 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
899
924
900 # Run the same check twice to verify that the transformer is not
925 # Run the same check twice to verify that the transformer is not
901 # disabled after raising.
926 # disabled after raising.
902 with expect_exception_tb, expect_no_cell_output:
927 with expect_exception_tb, expect_no_cell_output:
903 ip.run_cell("'unsafe'")
928 ip.run_cell("'unsafe'")
904
929
905 with expect_exception_tb, expect_no_cell_output:
930 with expect_exception_tb, expect_no_cell_output:
906 res = ip.run_cell("'unsafe'")
931 res = ip.run_cell("'unsafe'")
907
932
908 self.assertIsInstance(res.error_before_exec, InputRejected)
933 self.assertIsInstance(res.error_before_exec, InputRejected)
909
934
910 def test__IPYTHON__():
935 def test__IPYTHON__():
911 # This shouldn't raise a NameError, that's all
936 # This shouldn't raise a NameError, that's all
912 __IPYTHON__
937 __IPYTHON__
913
938
914
939
915 class DummyRepr(object):
940 class DummyRepr(object):
916 def __repr__(self):
941 def __repr__(self):
917 return "DummyRepr"
942 return "DummyRepr"
918
943
919 def _repr_html_(self):
944 def _repr_html_(self):
920 return "<b>dummy</b>"
945 return "<b>dummy</b>"
921
946
922 def _repr_javascript_(self):
947 def _repr_javascript_(self):
923 return "console.log('hi');", {'key': 'value'}
948 return "console.log('hi');", {'key': 'value'}
924
949
925
950
926 def test_user_variables():
951 def test_user_variables():
927 # enable all formatters
952 # enable all formatters
928 ip.display_formatter.active_types = ip.display_formatter.format_types
953 ip.display_formatter.active_types = ip.display_formatter.format_types
929
954
930 ip.user_ns['dummy'] = d = DummyRepr()
955 ip.user_ns['dummy'] = d = DummyRepr()
931 keys = {'dummy', 'doesnotexist'}
956 keys = {'dummy', 'doesnotexist'}
932 r = ip.user_expressions({ key:key for key in keys})
957 r = ip.user_expressions({ key:key for key in keys})
933
958
934 assert keys == set(r.keys())
959 assert keys == set(r.keys())
935 dummy = r["dummy"]
960 dummy = r["dummy"]
936 assert {"status", "data", "metadata"} == set(dummy.keys())
961 assert {"status", "data", "metadata"} == set(dummy.keys())
937 assert dummy["status"] == "ok"
962 assert dummy["status"] == "ok"
938 data = dummy["data"]
963 data = dummy["data"]
939 metadata = dummy["metadata"]
964 metadata = dummy["metadata"]
940 assert data.get("text/html") == d._repr_html_()
965 assert data.get("text/html") == d._repr_html_()
941 js, jsmd = d._repr_javascript_()
966 js, jsmd = d._repr_javascript_()
942 assert data.get("application/javascript") == js
967 assert data.get("application/javascript") == js
943 assert metadata.get("application/javascript") == jsmd
968 assert metadata.get("application/javascript") == jsmd
944
969
945 dne = r["doesnotexist"]
970 dne = r["doesnotexist"]
946 assert dne["status"] == "error"
971 assert dne["status"] == "error"
947 assert dne["ename"] == "NameError"
972 assert dne["ename"] == "NameError"
948
973
949 # back to text only
974 # back to text only
950 ip.display_formatter.active_types = ['text/plain']
975 ip.display_formatter.active_types = ['text/plain']
951
976
952 def test_user_expression():
977 def test_user_expression():
953 # enable all formatters
978 # enable all formatters
954 ip.display_formatter.active_types = ip.display_formatter.format_types
979 ip.display_formatter.active_types = ip.display_formatter.format_types
955 query = {
980 query = {
956 'a' : '1 + 2',
981 'a' : '1 + 2',
957 'b' : '1/0',
982 'b' : '1/0',
958 }
983 }
959 r = ip.user_expressions(query)
984 r = ip.user_expressions(query)
960 import pprint
985 import pprint
961 pprint.pprint(r)
986 pprint.pprint(r)
962 assert set(r.keys()) == set(query.keys())
987 assert set(r.keys()) == set(query.keys())
963 a = r["a"]
988 a = r["a"]
964 assert {"status", "data", "metadata"} == set(a.keys())
989 assert {"status", "data", "metadata"} == set(a.keys())
965 assert a["status"] == "ok"
990 assert a["status"] == "ok"
966 data = a["data"]
991 data = a["data"]
967 metadata = a["metadata"]
992 metadata = a["metadata"]
968 assert data.get("text/plain") == "3"
993 assert data.get("text/plain") == "3"
969
994
970 b = r["b"]
995 b = r["b"]
971 assert b["status"] == "error"
996 assert b["status"] == "error"
972 assert b["ename"] == "ZeroDivisionError"
997 assert b["ename"] == "ZeroDivisionError"
973
998
974 # back to text only
999 # back to text only
975 ip.display_formatter.active_types = ['text/plain']
1000 ip.display_formatter.active_types = ['text/plain']
976
1001
977
1002
978 class TestSyntaxErrorTransformer(unittest.TestCase):
1003 class TestSyntaxErrorTransformer(unittest.TestCase):
979 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1004 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
980
1005
981 @staticmethod
1006 @staticmethod
982 def transformer(lines):
1007 def transformer(lines):
983 for line in lines:
1008 for line in lines:
984 pos = line.find('syntaxerror')
1009 pos = line.find('syntaxerror')
985 if pos >= 0:
1010 if pos >= 0:
986 e = SyntaxError('input contains "syntaxerror"')
1011 e = SyntaxError('input contains "syntaxerror"')
987 e.text = line
1012 e.text = line
988 e.offset = pos + 1
1013 e.offset = pos + 1
989 raise e
1014 raise e
990 return lines
1015 return lines
991
1016
992 def setUp(self):
1017 def setUp(self):
993 ip.input_transformers_post.append(self.transformer)
1018 ip.input_transformers_post.append(self.transformer)
994
1019
995 def tearDown(self):
1020 def tearDown(self):
996 ip.input_transformers_post.remove(self.transformer)
1021 ip.input_transformers_post.remove(self.transformer)
997
1022
998 def test_syntaxerror_input_transformer(self):
1023 def test_syntaxerror_input_transformer(self):
999 with tt.AssertPrints('1234'):
1024 with tt.AssertPrints('1234'):
1000 ip.run_cell('1234')
1025 ip.run_cell('1234')
1001 with tt.AssertPrints('SyntaxError: invalid syntax'):
1026 with tt.AssertPrints('SyntaxError: invalid syntax'):
1002 ip.run_cell('1 2 3') # plain python syntax error
1027 ip.run_cell('1 2 3') # plain python syntax error
1003 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1028 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1004 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1029 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1005 with tt.AssertPrints('3456'):
1030 with tt.AssertPrints('3456'):
1006 ip.run_cell('3456')
1031 ip.run_cell('3456')
1007
1032
1008
1033
1009 class TestWarningSuppression(unittest.TestCase):
1034 class TestWarningSuppression(unittest.TestCase):
1010 def test_warning_suppression(self):
1035 def test_warning_suppression(self):
1011 ip.run_cell("import warnings")
1036 ip.run_cell("import warnings")
1012 try:
1037 try:
1013 with self.assertWarnsRegex(UserWarning, "asdf"):
1038 with self.assertWarnsRegex(UserWarning, "asdf"):
1014 ip.run_cell("warnings.warn('asdf')")
1039 ip.run_cell("warnings.warn('asdf')")
1015 # Here's the real test -- if we run that again, we should get the
1040 # Here's the real test -- if we run that again, we should get the
1016 # warning again. Traditionally, each warning was only issued once per
1041 # warning again. Traditionally, each warning was only issued once per
1017 # IPython session (approximately), even if the user typed in new and
1042 # IPython session (approximately), even if the user typed in new and
1018 # different code that should have also triggered the warning, leading
1043 # different code that should have also triggered the warning, leading
1019 # to much confusion.
1044 # to much confusion.
1020 with self.assertWarnsRegex(UserWarning, "asdf"):
1045 with self.assertWarnsRegex(UserWarning, "asdf"):
1021 ip.run_cell("warnings.warn('asdf')")
1046 ip.run_cell("warnings.warn('asdf')")
1022 finally:
1047 finally:
1023 ip.run_cell("del warnings")
1048 ip.run_cell("del warnings")
1024
1049
1025
1050
1026 def test_deprecation_warning(self):
1051 def test_deprecation_warning(self):
1027 ip.run_cell("""
1052 ip.run_cell("""
1028 import warnings
1053 import warnings
1029 def wrn():
1054 def wrn():
1030 warnings.warn(
1055 warnings.warn(
1031 "I AM A WARNING",
1056 "I AM A WARNING",
1032 DeprecationWarning
1057 DeprecationWarning
1033 )
1058 )
1034 """)
1059 """)
1035 try:
1060 try:
1036 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1061 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1037 ip.run_cell("wrn()")
1062 ip.run_cell("wrn()")
1038 finally:
1063 finally:
1039 ip.run_cell("del warnings")
1064 ip.run_cell("del warnings")
1040 ip.run_cell("del wrn")
1065 ip.run_cell("del wrn")
1041
1066
1042
1067
1043 class TestImportNoDeprecate(tt.TempFileMixin):
1068 class TestImportNoDeprecate(tt.TempFileMixin):
1044
1069
1045 def setUp(self):
1070 def setUp(self):
1046 """Make a valid python temp file."""
1071 """Make a valid python temp file."""
1047 self.mktmp("""
1072 self.mktmp("""
1048 import warnings
1073 import warnings
1049 def wrn():
1074 def wrn():
1050 warnings.warn(
1075 warnings.warn(
1051 "I AM A WARNING",
1076 "I AM A WARNING",
1052 DeprecationWarning
1077 DeprecationWarning
1053 )
1078 )
1054 """)
1079 """)
1055 super().setUp()
1080 super().setUp()
1056
1081
1057 def test_no_dep(self):
1082 def test_no_dep(self):
1058 """
1083 """
1059 No deprecation warning should be raised from imported functions
1084 No deprecation warning should be raised from imported functions
1060 """
1085 """
1061 ip.run_cell("from {} import wrn".format(self.fname))
1086 ip.run_cell("from {} import wrn".format(self.fname))
1062
1087
1063 with tt.AssertNotPrints("I AM A WARNING"):
1088 with tt.AssertNotPrints("I AM A WARNING"):
1064 ip.run_cell("wrn()")
1089 ip.run_cell("wrn()")
1065 ip.run_cell("del wrn")
1090 ip.run_cell("del wrn")
1066
1091
1067
1092
1068 def test_custom_exc_count():
1093 def test_custom_exc_count():
1069 hook = mock.Mock(return_value=None)
1094 hook = mock.Mock(return_value=None)
1070 ip.set_custom_exc((SyntaxError,), hook)
1095 ip.set_custom_exc((SyntaxError,), hook)
1071 before = ip.execution_count
1096 before = ip.execution_count
1072 ip.run_cell("def foo()", store_history=True)
1097 ip.run_cell("def foo()", store_history=True)
1073 # restore default excepthook
1098 # restore default excepthook
1074 ip.set_custom_exc((), None)
1099 ip.set_custom_exc((), None)
1075 assert hook.call_count == 1
1100 assert hook.call_count == 1
1076 assert ip.execution_count == before + 1
1101 assert ip.execution_count == before + 1
1077
1102
1078
1103
1079 def test_run_cell_async():
1104 def test_run_cell_async():
1080 ip.run_cell("import asyncio")
1105 ip.run_cell("import asyncio")
1081 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1106 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1082 assert asyncio.iscoroutine(coro)
1107 assert asyncio.iscoroutine(coro)
1083 loop = asyncio.new_event_loop()
1108 loop = asyncio.new_event_loop()
1084 result = loop.run_until_complete(coro)
1109 result = loop.run_until_complete(coro)
1085 assert isinstance(result, interactiveshell.ExecutionResult)
1110 assert isinstance(result, interactiveshell.ExecutionResult)
1086 assert result.result == 5
1111 assert result.result == 5
1087
1112
1088
1113
1089 def test_run_cell_await():
1114 def test_run_cell_await():
1090 ip.run_cell("import asyncio")
1115 ip.run_cell("import asyncio")
1091 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1116 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1092 assert ip.user_ns["_"] == 10
1117 assert ip.user_ns["_"] == 10
1093
1118
1094
1119
1095 def test_run_cell_asyncio_run():
1120 def test_run_cell_asyncio_run():
1096 ip.run_cell("import asyncio")
1121 ip.run_cell("import asyncio")
1097 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1122 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1098 assert ip.user_ns["_"] == 1
1123 assert ip.user_ns["_"] == 1
1099 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1124 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1100 assert ip.user_ns["_"] == 2
1125 assert ip.user_ns["_"] == 2
1101 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1126 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1102 assert ip.user_ns["_"] == 3
1127 assert ip.user_ns["_"] == 3
1103
1128
1104
1129
1105 def test_should_run_async():
1130 def test_should_run_async():
1106 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1131 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1107 assert ip.should_run_async("await x", transformed_cell="await x")
1132 assert ip.should_run_async("await x", transformed_cell="await x")
1108 assert ip.should_run_async(
1133 assert ip.should_run_async(
1109 "import asyncio; await asyncio.sleep(1)",
1134 "import asyncio; await asyncio.sleep(1)",
1110 transformed_cell="import asyncio; await asyncio.sleep(1)",
1135 transformed_cell="import asyncio; await asyncio.sleep(1)",
1111 )
1136 )
1112
1137
1113
1138
1114 def test_set_custom_completer():
1139 def test_set_custom_completer():
1115 num_completers = len(ip.Completer.matchers)
1140 num_completers = len(ip.Completer.matchers)
1116
1141
1117 def foo(*args, **kwargs):
1142 def foo(*args, **kwargs):
1118 return "I'm a completer!"
1143 return "I'm a completer!"
1119
1144
1120 ip.set_custom_completer(foo, 0)
1145 ip.set_custom_completer(foo, 0)
1121
1146
1122 # check that we've really added a new completer
1147 # check that we've really added a new completer
1123 assert len(ip.Completer.matchers) == num_completers + 1
1148 assert len(ip.Completer.matchers) == num_completers + 1
1124
1149
1125 # check that the first completer is the function we defined
1150 # check that the first completer is the function we defined
1126 assert ip.Completer.matchers[0]() == "I'm a completer!"
1151 assert ip.Completer.matchers[0]() == "I'm a completer!"
1127
1152
1128 # clean up
1153 # clean up
1129 ip.Completer.custom_matchers.pop()
1154 ip.Completer.custom_matchers.pop()
1130
1155
1131
1156
1132 class TestShowTracebackAttack(unittest.TestCase):
1157 class TestShowTracebackAttack(unittest.TestCase):
1133 """Test that the interactive shell is resilient against the client attack of
1158 """Test that the interactive shell is resilient against the client attack of
1134 manipulating the showtracebacks method. These attacks shouldn't result in an
1159 manipulating the showtracebacks method. These attacks shouldn't result in an
1135 unhandled exception in the kernel."""
1160 unhandled exception in the kernel."""
1136
1161
1137 def setUp(self):
1162 def setUp(self):
1138 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1163 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1139
1164
1140 def tearDown(self):
1165 def tearDown(self):
1141 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1166 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1142
1167
1143 def test_set_show_tracebacks_none(self):
1168 def test_set_show_tracebacks_none(self):
1144 """Test the case of the client setting showtracebacks to None"""
1169 """Test the case of the client setting showtracebacks to None"""
1145
1170
1146 result = ip.run_cell(
1171 result = ip.run_cell(
1147 """
1172 """
1148 import IPython.core.interactiveshell
1173 import IPython.core.interactiveshell
1149 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1174 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1150
1175
1151 assert False, "This should not raise an exception"
1176 assert False, "This should not raise an exception"
1152 """
1177 """
1153 )
1178 )
1154 print(result)
1179 print(result)
1155
1180
1156 assert result.result is None
1181 assert result.result is None
1157 assert isinstance(result.error_in_exec, TypeError)
1182 assert isinstance(result.error_in_exec, TypeError)
1158 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1183 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1159
1184
1160 def test_set_show_tracebacks_noop(self):
1185 def test_set_show_tracebacks_noop(self):
1161 """Test the case of the client setting showtracebacks to a no op lambda"""
1186 """Test the case of the client setting showtracebacks to a no op lambda"""
1162
1187
1163 result = ip.run_cell(
1188 result = ip.run_cell(
1164 """
1189 """
1165 import IPython.core.interactiveshell
1190 import IPython.core.interactiveshell
1166 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1191 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1167
1192
1168 assert False, "This should not raise an exception"
1193 assert False, "This should not raise an exception"
1169 """
1194 """
1170 )
1195 )
1171 print(result)
1196 print(result)
1172
1197
1173 assert result.result is None
1198 assert result.result is None
1174 assert isinstance(result.error_in_exec, AssertionError)
1199 assert isinstance(result.error_in_exec, AssertionError)
1175 assert str(result.error_in_exec) == "This should not raise an exception"
1200 assert str(result.error_in_exec) == "This should not raise an exception"
General Comments 0
You need to be logged in to leave comments. Login now