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 |
|
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 |
|
|
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) |
|
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 = |
|
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 = |
|
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( |
|
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( |
|
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( |
|
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( |
|
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 |
|
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