##// END OF EJS Templates
Handle OSError from inspect.getsource/getsourcelines (bpo-44648)...
Nikita Kniazev -
Show More
@@ -1,1047 +1,1052 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 linecache
19 import linecache
20 import warnings
20 import warnings
21 import os
21 import os
22 from textwrap import dedent
22 from textwrap import dedent
23 import types
23 import types
24 import io as stdlib_io
24 import io as stdlib_io
25
25
26 from typing import Union
26 from typing import Union
27
27
28 # IPython's own
28 # IPython's own
29 from IPython.core import page
29 from IPython.core import page
30 from IPython.lib.pretty import pretty
30 from IPython.lib.pretty import pretty
31 from IPython.testing.skipdoctest import skip_doctest
31 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.utils import PyColorize
32 from IPython.utils import PyColorize
33 from IPython.utils import openpy
33 from IPython.utils import openpy
34 from IPython.utils import py3compat
34 from IPython.utils import py3compat
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 def pylight(code):
49 def pylight(code):
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51
51
52 # builtin docstrings to ignore
52 # builtin docstrings to ignore
53 _func_call_docstring = types.FunctionType.__call__.__doc__
53 _func_call_docstring = types.FunctionType.__call__.__doc__
54 _object_init_docstring = object.__init__.__doc__
54 _object_init_docstring = object.__init__.__doc__
55 _builtin_type_docstrings = {
55 _builtin_type_docstrings = {
56 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
56 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
57 types.FunctionType, property)
57 types.FunctionType, property)
58 }
58 }
59
59
60 _builtin_func_type = type(all)
60 _builtin_func_type = type(all)
61 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
61 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
62 #****************************************************************************
62 #****************************************************************************
63 # Builtin color schemes
63 # Builtin color schemes
64
64
65 Colors = TermColors # just a shorthand
65 Colors = TermColors # just a shorthand
66
66
67 InspectColors = PyColorize.ANSICodeColors
67 InspectColors = PyColorize.ANSICodeColors
68
68
69 #****************************************************************************
69 #****************************************************************************
70 # Auxiliary functions and objects
70 # Auxiliary functions and objects
71
71
72 # See the messaging spec for the definition of all these fields. This list
72 # See the messaging spec for the definition of all these fields. This list
73 # effectively defines the order of display
73 # effectively defines the order of display
74 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
74 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
75 'length', 'file', 'definition', 'docstring', 'source',
75 'length', 'file', 'definition', 'docstring', 'source',
76 'init_definition', 'class_docstring', 'init_docstring',
76 'init_definition', 'class_docstring', 'init_docstring',
77 'call_def', 'call_docstring',
77 'call_def', 'call_docstring',
78 # These won't be printed but will be used to determine how to
78 # These won't be printed but will be used to determine how to
79 # format the object
79 # format the object
80 'ismagic', 'isalias', 'isclass', 'found', 'name'
80 'ismagic', 'isalias', 'isclass', 'found', 'name'
81 ]
81 ]
82
82
83
83
84 def object_info(**kw):
84 def object_info(**kw):
85 """Make an object info dict with all fields present."""
85 """Make an object info dict with all fields present."""
86 infodict = {k:None for k in info_fields}
86 infodict = {k:None for k in info_fields}
87 infodict.update(kw)
87 infodict.update(kw)
88 return infodict
88 return infodict
89
89
90
90
91 def get_encoding(obj):
91 def get_encoding(obj):
92 """Get encoding for python source file defining obj
92 """Get encoding for python source file defining obj
93
93
94 Returns None if obj is not defined in a sourcefile.
94 Returns None if obj is not defined in a sourcefile.
95 """
95 """
96 ofile = find_file(obj)
96 ofile = find_file(obj)
97 # run contents of file through pager starting at line where the object
97 # 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
98 # is defined, as long as the file isn't binary and is actually on the
99 # filesystem.
99 # filesystem.
100 if ofile is None:
100 if ofile is None:
101 return None
101 return None
102 elif ofile.endswith(('.so', '.dll', '.pyd')):
102 elif ofile.endswith(('.so', '.dll', '.pyd')):
103 return None
103 return None
104 elif not os.path.isfile(ofile):
104 elif not os.path.isfile(ofile):
105 return None
105 return None
106 else:
106 else:
107 # Print only text files, not extension binaries. Note that
107 # Print only text files, not extension binaries. Note that
108 # getsourcelines returns lineno with 1-offset and page() uses
108 # getsourcelines returns lineno with 1-offset and page() uses
109 # 0-offset, so we must adjust.
109 # 0-offset, so we must adjust.
110 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
110 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
111 encoding, lines = openpy.detect_encoding(buffer.readline)
111 encoding, lines = openpy.detect_encoding(buffer.readline)
112 return encoding
112 return encoding
113
113
114 def getdoc(obj) -> Union[str,None]:
114 def getdoc(obj) -> Union[str,None]:
115 """Stable wrapper around inspect.getdoc.
115 """Stable wrapper around inspect.getdoc.
116
116
117 This can't crash because of attribute problems.
117 This can't crash because of attribute problems.
118
118
119 It also attempts to call a getdoc() method on the given object. This
119 It also attempts to call a getdoc() method on the given object. This
120 allows objects which provide their docstrings via non-standard mechanisms
120 allows objects which provide their docstrings via non-standard mechanisms
121 (like Pyro proxies) to still be inspected by ipython's ? system.
121 (like Pyro proxies) to still be inspected by ipython's ? system.
122 """
122 """
123 # Allow objects to offer customized documentation via a getdoc method:
123 # Allow objects to offer customized documentation via a getdoc method:
124 try:
124 try:
125 ds = obj.getdoc()
125 ds = obj.getdoc()
126 except Exception:
126 except Exception:
127 pass
127 pass
128 else:
128 else:
129 if isinstance(ds, str):
129 if isinstance(ds, str):
130 return inspect.cleandoc(ds)
130 return inspect.cleandoc(ds)
131 docstr = inspect.getdoc(obj)
131 docstr = inspect.getdoc(obj)
132 return docstr
132 return docstr
133
133
134
134
135 def getsource(obj, oname='') -> Union[str,None]:
135 def getsource(obj, oname='') -> Union[str,None]:
136 """Wrapper around inspect.getsource.
136 """Wrapper around inspect.getsource.
137
137
138 This can be modified by other projects to provide customized source
138 This can be modified by other projects to provide customized source
139 extraction.
139 extraction.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 obj : object
143 obj : object
144 an object whose source code we will attempt to extract
144 an object whose source code we will attempt to extract
145 oname : str
145 oname : str
146 (optional) a name under which the object is known
146 (optional) a name under which the object is known
147
147
148 Returns
148 Returns
149 -------
149 -------
150 src : unicode or None
150 src : unicode or None
151
151
152 """
152 """
153
153
154 if isinstance(obj, property):
154 if isinstance(obj, property):
155 sources = []
155 sources = []
156 for attrname in ['fget', 'fset', 'fdel']:
156 for attrname in ['fget', 'fset', 'fdel']:
157 fn = getattr(obj, attrname)
157 fn = getattr(obj, attrname)
158 if fn is not None:
158 if fn is not None:
159 encoding = get_encoding(fn)
159 encoding = get_encoding(fn)
160 oname_prefix = ('%s.' % oname) if oname else ''
160 oname_prefix = ('%s.' % oname) if oname else ''
161 sources.append(''.join(('# ', oname_prefix, attrname)))
161 sources.append(''.join(('# ', oname_prefix, attrname)))
162 if inspect.isfunction(fn):
162 if inspect.isfunction(fn):
163 sources.append(dedent(getsource(fn)))
163 sources.append(dedent(getsource(fn)))
164 else:
164 else:
165 # Default str/repr only prints function name,
165 # Default str/repr only prints function name,
166 # pretty.pretty prints module name too.
166 # pretty.pretty prints module name too.
167 sources.append(
167 sources.append(
168 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
168 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
169 )
169 )
170 if sources:
170 if sources:
171 return '\n'.join(sources)
171 return '\n'.join(sources)
172 else:
172 else:
173 return None
173 return None
174
174
175 else:
175 else:
176 # Get source for non-property objects.
176 # Get source for non-property objects.
177
177
178 obj = _get_wrapped(obj)
178 obj = _get_wrapped(obj)
179
179
180 try:
180 try:
181 src = inspect.getsource(obj)
181 src = inspect.getsource(obj)
182 except TypeError:
182 except TypeError:
183 # The object itself provided no meaningful source, try looking for
183 # The object itself provided no meaningful source, try looking for
184 # its class definition instead.
184 # its class definition instead.
185 try:
185 try:
186 src = inspect.getsource(obj.__class__)
186 src = inspect.getsource(obj.__class__)
187 except TypeError:
187 except (OSError, TypeError):
188 return None
188 return None
189 except OSError:
190 return None
189
191
190 return src
192 return src
191
193
192
194
193 def is_simple_callable(obj):
195 def is_simple_callable(obj):
194 """True if obj is a function ()"""
196 """True if obj is a function ()"""
195 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
197 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
196 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
198 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
197
199
198 @undoc
200 @undoc
199 def getargspec(obj):
201 def getargspec(obj):
200 """Wrapper around :func:`inspect.getfullargspec`
202 """Wrapper around :func:`inspect.getfullargspec`
201
203
202 In addition to functions and methods, this can also handle objects with a
204 In addition to functions and methods, this can also handle objects with a
203 ``__call__`` attribute.
205 ``__call__`` attribute.
204
206
205 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
207 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
206 """
208 """
207
209
208 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
210 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
209 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
211 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
210
212
211 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
213 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
212 obj = obj.__call__
214 obj = obj.__call__
213
215
214 return inspect.getfullargspec(obj)
216 return inspect.getfullargspec(obj)
215
217
216 @undoc
218 @undoc
217 def format_argspec(argspec):
219 def format_argspec(argspec):
218 """Format argspect, convenience wrapper around inspect's.
220 """Format argspect, convenience wrapper around inspect's.
219
221
220 This takes a dict instead of ordered arguments and calls
222 This takes a dict instead of ordered arguments and calls
221 inspect.format_argspec with the arguments in the necessary order.
223 inspect.format_argspec with the arguments in the necessary order.
222
224
223 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
225 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
224 """
226 """
225
227
226 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
228 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
227 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
229 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
228
230
229
231
230 return inspect.formatargspec(argspec['args'], argspec['varargs'],
232 return inspect.formatargspec(argspec['args'], argspec['varargs'],
231 argspec['varkw'], argspec['defaults'])
233 argspec['varkw'], argspec['defaults'])
232
234
233 @undoc
235 @undoc
234 def call_tip(oinfo, format_call=True):
236 def call_tip(oinfo, format_call=True):
235 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
237 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
236 warnings.warn(
238 warnings.warn(
237 "`call_tip` function is deprecated as of IPython 6.0"
239 "`call_tip` function is deprecated as of IPython 6.0"
238 "and will be removed in future versions.",
240 "and will be removed in future versions.",
239 DeprecationWarning,
241 DeprecationWarning,
240 stacklevel=2,
242 stacklevel=2,
241 )
243 )
242 # Get call definition
244 # Get call definition
243 argspec = oinfo.get('argspec')
245 argspec = oinfo.get('argspec')
244 if argspec is None:
246 if argspec is None:
245 call_line = None
247 call_line = None
246 else:
248 else:
247 # Callable objects will have 'self' as their first argument, prune
249 # Callable objects will have 'self' as their first argument, prune
248 # it out if it's there for clarity (since users do *not* pass an
250 # it out if it's there for clarity (since users do *not* pass an
249 # extra first argument explicitly).
251 # extra first argument explicitly).
250 try:
252 try:
251 has_self = argspec['args'][0] == 'self'
253 has_self = argspec['args'][0] == 'self'
252 except (KeyError, IndexError):
254 except (KeyError, IndexError):
253 pass
255 pass
254 else:
256 else:
255 if has_self:
257 if has_self:
256 argspec['args'] = argspec['args'][1:]
258 argspec['args'] = argspec['args'][1:]
257
259
258 call_line = oinfo['name']+format_argspec(argspec)
260 call_line = oinfo['name']+format_argspec(argspec)
259
261
260 # Now get docstring.
262 # Now get docstring.
261 # The priority is: call docstring, constructor docstring, main one.
263 # The priority is: call docstring, constructor docstring, main one.
262 doc = oinfo.get('call_docstring')
264 doc = oinfo.get('call_docstring')
263 if doc is None:
265 if doc is None:
264 doc = oinfo.get('init_docstring')
266 doc = oinfo.get('init_docstring')
265 if doc is None:
267 if doc is None:
266 doc = oinfo.get('docstring','')
268 doc = oinfo.get('docstring','')
267
269
268 return call_line, doc
270 return call_line, doc
269
271
270
272
271 def _get_wrapped(obj):
273 def _get_wrapped(obj):
272 """Get the original object if wrapped in one or more @decorators
274 """Get the original object if wrapped in one or more @decorators
273
275
274 Some objects automatically construct similar objects on any unrecognised
276 Some objects automatically construct similar objects on any unrecognised
275 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
277 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
276 this will arbitrarily cut off after 100 levels of obj.__wrapped__
278 this will arbitrarily cut off after 100 levels of obj.__wrapped__
277 attribute access. --TK, Jan 2016
279 attribute access. --TK, Jan 2016
278 """
280 """
279 orig_obj = obj
281 orig_obj = obj
280 i = 0
282 i = 0
281 while safe_hasattr(obj, '__wrapped__'):
283 while safe_hasattr(obj, '__wrapped__'):
282 obj = obj.__wrapped__
284 obj = obj.__wrapped__
283 i += 1
285 i += 1
284 if i > 100:
286 if i > 100:
285 # __wrapped__ is probably a lie, so return the thing we started with
287 # __wrapped__ is probably a lie, so return the thing we started with
286 return orig_obj
288 return orig_obj
287 return obj
289 return obj
288
290
289 def find_file(obj) -> str:
291 def find_file(obj) -> str:
290 """Find the absolute path to the file where an object was defined.
292 """Find the absolute path to the file where an object was defined.
291
293
292 This is essentially a robust wrapper around `inspect.getabsfile`.
294 This is essentially a robust wrapper around `inspect.getabsfile`.
293
295
294 Returns None if no file can be found.
296 Returns None if no file can be found.
295
297
296 Parameters
298 Parameters
297 ----------
299 ----------
298 obj : any Python object
300 obj : any Python object
299
301
300 Returns
302 Returns
301 -------
303 -------
302 fname : str
304 fname : str
303 The absolute path to the file where the object was defined.
305 The absolute path to the file where the object was defined.
304 """
306 """
305 obj = _get_wrapped(obj)
307 obj = _get_wrapped(obj)
306
308
307 fname = None
309 fname = None
308 try:
310 try:
309 fname = inspect.getabsfile(obj)
311 fname = inspect.getabsfile(obj)
310 except TypeError:
312 except TypeError:
311 # For an instance, the file that matters is where its class was
313 # For an instance, the file that matters is where its class was
312 # declared.
314 # declared.
313 try:
315 try:
314 fname = inspect.getabsfile(obj.__class__)
316 fname = inspect.getabsfile(obj.__class__)
315 except (OSError, TypeError):
317 except (OSError, TypeError):
316 # Can happen for builtins
318 # Can happen for builtins
317 pass
319 pass
318 except:
320 except OSError:
319 pass
321 pass
322
320 return cast_unicode(fname)
323 return cast_unicode(fname)
321
324
322
325
323 def find_source_lines(obj):
326 def find_source_lines(obj):
324 """Find the line number in a file where an object was defined.
327 """Find the line number in a file where an object was defined.
325
328
326 This is essentially a robust wrapper around `inspect.getsourcelines`.
329 This is essentially a robust wrapper around `inspect.getsourcelines`.
327
330
328 Returns None if no file can be found.
331 Returns None if no file can be found.
329
332
330 Parameters
333 Parameters
331 ----------
334 ----------
332 obj : any Python object
335 obj : any Python object
333
336
334 Returns
337 Returns
335 -------
338 -------
336 lineno : int
339 lineno : int
337 The line number where the object definition starts.
340 The line number where the object definition starts.
338 """
341 """
339 obj = _get_wrapped(obj)
342 obj = _get_wrapped(obj)
340
343
341 try:
344 try:
345 lineno = inspect.getsourcelines(obj)[1]
346 except TypeError:
347 # For instances, try the class object like getsource() does
342 try:
348 try:
343 lineno = inspect.getsourcelines(obj)[1]
344 except TypeError:
345 # For instances, try the class object like getsource() does
346 lineno = inspect.getsourcelines(obj.__class__)[1]
349 lineno = inspect.getsourcelines(obj.__class__)[1]
347 except:
350 except (OSError, TypeError):
351 return None
352 except OSError:
348 return None
353 return None
349
354
350 return lineno
355 return lineno
351
356
352 class Inspector(Colorable):
357 class Inspector(Colorable):
353
358
354 def __init__(self, color_table=InspectColors,
359 def __init__(self, color_table=InspectColors,
355 code_color_table=PyColorize.ANSICodeColors,
360 code_color_table=PyColorize.ANSICodeColors,
356 scheme=None,
361 scheme=None,
357 str_detail_level=0,
362 str_detail_level=0,
358 parent=None, config=None):
363 parent=None, config=None):
359 super(Inspector, self).__init__(parent=parent, config=config)
364 super(Inspector, self).__init__(parent=parent, config=config)
360 self.color_table = color_table
365 self.color_table = color_table
361 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
366 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
362 self.format = self.parser.format
367 self.format = self.parser.format
363 self.str_detail_level = str_detail_level
368 self.str_detail_level = str_detail_level
364 self.set_active_scheme(scheme)
369 self.set_active_scheme(scheme)
365
370
366 def _getdef(self,obj,oname='') -> Union[str,None]:
371 def _getdef(self,obj,oname='') -> Union[str,None]:
367 """Return the call signature for any callable object.
372 """Return the call signature for any callable object.
368
373
369 If any exception is generated, None is returned instead and the
374 If any exception is generated, None is returned instead and the
370 exception is suppressed."""
375 exception is suppressed."""
371 try:
376 try:
372 return _render_signature(signature(obj), oname)
377 return _render_signature(signature(obj), oname)
373 except:
378 except:
374 return None
379 return None
375
380
376 def __head(self,h) -> str:
381 def __head(self,h) -> str:
377 """Return a header string with proper colors."""
382 """Return a header string with proper colors."""
378 return '%s%s%s' % (self.color_table.active_colors.header,h,
383 return '%s%s%s' % (self.color_table.active_colors.header,h,
379 self.color_table.active_colors.normal)
384 self.color_table.active_colors.normal)
380
385
381 def set_active_scheme(self, scheme):
386 def set_active_scheme(self, scheme):
382 if scheme is not None:
387 if scheme is not None:
383 self.color_table.set_active_scheme(scheme)
388 self.color_table.set_active_scheme(scheme)
384 self.parser.color_table.set_active_scheme(scheme)
389 self.parser.color_table.set_active_scheme(scheme)
385
390
386 def noinfo(self, msg, oname):
391 def noinfo(self, msg, oname):
387 """Generic message when no information is found."""
392 """Generic message when no information is found."""
388 print('No %s found' % msg, end=' ')
393 print('No %s found' % msg, end=' ')
389 if oname:
394 if oname:
390 print('for %s' % oname)
395 print('for %s' % oname)
391 else:
396 else:
392 print()
397 print()
393
398
394 def pdef(self, obj, oname=''):
399 def pdef(self, obj, oname=''):
395 """Print the call signature for any callable object.
400 """Print the call signature for any callable object.
396
401
397 If the object is a class, print the constructor information."""
402 If the object is a class, print the constructor information."""
398
403
399 if not callable(obj):
404 if not callable(obj):
400 print('Object is not callable.')
405 print('Object is not callable.')
401 return
406 return
402
407
403 header = ''
408 header = ''
404
409
405 if inspect.isclass(obj):
410 if inspect.isclass(obj):
406 header = self.__head('Class constructor information:\n')
411 header = self.__head('Class constructor information:\n')
407
412
408
413
409 output = self._getdef(obj,oname)
414 output = self._getdef(obj,oname)
410 if output is None:
415 if output is None:
411 self.noinfo('definition header',oname)
416 self.noinfo('definition header',oname)
412 else:
417 else:
413 print(header,self.format(output), end=' ')
418 print(header,self.format(output), end=' ')
414
419
415 # In Python 3, all classes are new-style, so they all have __init__.
420 # In Python 3, all classes are new-style, so they all have __init__.
416 @skip_doctest
421 @skip_doctest
417 def pdoc(self, obj, oname='', formatter=None):
422 def pdoc(self, obj, oname='', formatter=None):
418 """Print the docstring for any object.
423 """Print the docstring for any object.
419
424
420 Optional:
425 Optional:
421 -formatter: a function to run the docstring through for specially
426 -formatter: a function to run the docstring through for specially
422 formatted docstrings.
427 formatted docstrings.
423
428
424 Examples
429 Examples
425 --------
430 --------
426
431
427 In [1]: class NoInit:
432 In [1]: class NoInit:
428 ...: pass
433 ...: pass
429
434
430 In [2]: class NoDoc:
435 In [2]: class NoDoc:
431 ...: def __init__(self):
436 ...: def __init__(self):
432 ...: pass
437 ...: pass
433
438
434 In [3]: %pdoc NoDoc
439 In [3]: %pdoc NoDoc
435 No documentation found for NoDoc
440 No documentation found for NoDoc
436
441
437 In [4]: %pdoc NoInit
442 In [4]: %pdoc NoInit
438 No documentation found for NoInit
443 No documentation found for NoInit
439
444
440 In [5]: obj = NoInit()
445 In [5]: obj = NoInit()
441
446
442 In [6]: %pdoc obj
447 In [6]: %pdoc obj
443 No documentation found for obj
448 No documentation found for obj
444
449
445 In [5]: obj2 = NoDoc()
450 In [5]: obj2 = NoDoc()
446
451
447 In [6]: %pdoc obj2
452 In [6]: %pdoc obj2
448 No documentation found for obj2
453 No documentation found for obj2
449 """
454 """
450
455
451 head = self.__head # For convenience
456 head = self.__head # For convenience
452 lines = []
457 lines = []
453 ds = getdoc(obj)
458 ds = getdoc(obj)
454 if formatter:
459 if formatter:
455 ds = formatter(ds).get('plain/text', ds)
460 ds = formatter(ds).get('plain/text', ds)
456 if ds:
461 if ds:
457 lines.append(head("Class docstring:"))
462 lines.append(head("Class docstring:"))
458 lines.append(indent(ds))
463 lines.append(indent(ds))
459 if inspect.isclass(obj) and hasattr(obj, '__init__'):
464 if inspect.isclass(obj) and hasattr(obj, '__init__'):
460 init_ds = getdoc(obj.__init__)
465 init_ds = getdoc(obj.__init__)
461 if init_ds is not None:
466 if init_ds is not None:
462 lines.append(head("Init docstring:"))
467 lines.append(head("Init docstring:"))
463 lines.append(indent(init_ds))
468 lines.append(indent(init_ds))
464 elif hasattr(obj,'__call__'):
469 elif hasattr(obj,'__call__'):
465 call_ds = getdoc(obj.__call__)
470 call_ds = getdoc(obj.__call__)
466 if call_ds:
471 if call_ds:
467 lines.append(head("Call docstring:"))
472 lines.append(head("Call docstring:"))
468 lines.append(indent(call_ds))
473 lines.append(indent(call_ds))
469
474
470 if not lines:
475 if not lines:
471 self.noinfo('documentation',oname)
476 self.noinfo('documentation',oname)
472 else:
477 else:
473 page.page('\n'.join(lines))
478 page.page('\n'.join(lines))
474
479
475 def psource(self, obj, oname=''):
480 def psource(self, obj, oname=''):
476 """Print the source code for an object."""
481 """Print the source code for an object."""
477
482
478 # Flush the source cache because inspect can return out-of-date source
483 # Flush the source cache because inspect can return out-of-date source
479 linecache.checkcache()
484 linecache.checkcache()
480 try:
485 try:
481 src = getsource(obj, oname=oname)
486 src = getsource(obj, oname=oname)
482 except Exception:
487 except Exception:
483 src = None
488 src = None
484
489
485 if src is None:
490 if src is None:
486 self.noinfo('source', oname)
491 self.noinfo('source', oname)
487 else:
492 else:
488 page.page(self.format(src))
493 page.page(self.format(src))
489
494
490 def pfile(self, obj, oname=''):
495 def pfile(self, obj, oname=''):
491 """Show the whole file where an object was defined."""
496 """Show the whole file where an object was defined."""
492
497
493 lineno = find_source_lines(obj)
498 lineno = find_source_lines(obj)
494 if lineno is None:
499 if lineno is None:
495 self.noinfo('file', oname)
500 self.noinfo('file', oname)
496 return
501 return
497
502
498 ofile = find_file(obj)
503 ofile = find_file(obj)
499 # run contents of file through pager starting at line where the object
504 # run contents of file through pager starting at line where the object
500 # is defined, as long as the file isn't binary and is actually on the
505 # is defined, as long as the file isn't binary and is actually on the
501 # filesystem.
506 # filesystem.
502 if ofile.endswith(('.so', '.dll', '.pyd')):
507 if ofile.endswith(('.so', '.dll', '.pyd')):
503 print('File %r is binary, not printing.' % ofile)
508 print('File %r is binary, not printing.' % ofile)
504 elif not os.path.isfile(ofile):
509 elif not os.path.isfile(ofile):
505 print('File %r does not exist, not printing.' % ofile)
510 print('File %r does not exist, not printing.' % ofile)
506 else:
511 else:
507 # Print only text files, not extension binaries. Note that
512 # Print only text files, not extension binaries. Note that
508 # getsourcelines returns lineno with 1-offset and page() uses
513 # getsourcelines returns lineno with 1-offset and page() uses
509 # 0-offset, so we must adjust.
514 # 0-offset, so we must adjust.
510 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
515 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
511
516
512
517
513 def _mime_format(self, text:str, formatter=None) -> dict:
518 def _mime_format(self, text:str, formatter=None) -> dict:
514 """Return a mime bundle representation of the input text.
519 """Return a mime bundle representation of the input text.
515
520
516 - if `formatter` is None, the returned mime bundle has
521 - if `formatter` is None, the returned mime bundle has
517 a `text/plain` field, with the input text.
522 a `text/plain` field, with the input text.
518 a `text/html` field with a `<pre>` tag containing the input text.
523 a `text/html` field with a `<pre>` tag containing the input text.
519
524
520 - if `formatter` is not None, it must be a callable transforming the
525 - if `formatter` is not None, it must be a callable transforming the
521 input text into a mime bundle. Default values for `text/plain` and
526 input text into a mime bundle. Default values for `text/plain` and
522 `text/html` representations are the ones described above.
527 `text/html` representations are the ones described above.
523
528
524 Note:
529 Note:
525
530
526 Formatters returning strings are supported but this behavior is deprecated.
531 Formatters returning strings are supported but this behavior is deprecated.
527
532
528 """
533 """
529 defaults = {
534 defaults = {
530 'text/plain': text,
535 'text/plain': text,
531 'text/html': '<pre>' + text + '</pre>'
536 'text/html': '<pre>' + text + '</pre>'
532 }
537 }
533
538
534 if formatter is None:
539 if formatter is None:
535 return defaults
540 return defaults
536 else:
541 else:
537 formatted = formatter(text)
542 formatted = formatter(text)
538
543
539 if not isinstance(formatted, dict):
544 if not isinstance(formatted, dict):
540 # Handle the deprecated behavior of a formatter returning
545 # Handle the deprecated behavior of a formatter returning
541 # a string instead of a mime bundle.
546 # a string instead of a mime bundle.
542 return {
547 return {
543 'text/plain': formatted,
548 'text/plain': formatted,
544 'text/html': '<pre>' + formatted + '</pre>'
549 'text/html': '<pre>' + formatted + '</pre>'
545 }
550 }
546
551
547 else:
552 else:
548 return dict(defaults, **formatted)
553 return dict(defaults, **formatted)
549
554
550
555
551 def format_mime(self, bundle):
556 def format_mime(self, bundle):
552
557
553 text_plain = bundle['text/plain']
558 text_plain = bundle['text/plain']
554
559
555 text = ''
560 text = ''
556 heads, bodies = list(zip(*text_plain))
561 heads, bodies = list(zip(*text_plain))
557 _len = max(len(h) for h in heads)
562 _len = max(len(h) for h in heads)
558
563
559 for head, body in zip(heads, bodies):
564 for head, body in zip(heads, bodies):
560 body = body.strip('\n')
565 body = body.strip('\n')
561 delim = '\n' if '\n' in body else ' '
566 delim = '\n' if '\n' in body else ' '
562 text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
567 text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
563
568
564 bundle['text/plain'] = text
569 bundle['text/plain'] = text
565 return bundle
570 return bundle
566
571
567 def _get_info(
572 def _get_info(
568 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
573 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
569 ):
574 ):
570 """Retrieve an info dict and format it.
575 """Retrieve an info dict and format it.
571
576
572 Parameters
577 Parameters
573 ==========
578 ==========
574
579
575 obj: any
580 obj: any
576 Object to inspect and return info from
581 Object to inspect and return info from
577 oname: str (default: ''):
582 oname: str (default: ''):
578 Name of the variable pointing to `obj`.
583 Name of the variable pointing to `obj`.
579 formatter: callable
584 formatter: callable
580 info:
585 info:
581 already computed information
586 already computed information
582 detail_level: integer
587 detail_level: integer
583 Granularity of detail level, if set to 1, give more information.
588 Granularity of detail level, if set to 1, give more information.
584 omit_sections: container[str]
589 omit_sections: container[str]
585 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
590 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
586 """
591 """
587
592
588 info = self._info(obj, oname=oname, info=info, detail_level=detail_level)
593 info = self._info(obj, oname=oname, info=info, detail_level=detail_level)
589
594
590 _mime = {
595 _mime = {
591 'text/plain': [],
596 'text/plain': [],
592 'text/html': '',
597 'text/html': '',
593 }
598 }
594
599
595 def append_field(bundle, title:str, key:str, formatter=None):
600 def append_field(bundle, title:str, key:str, formatter=None):
596 if title in omit_sections or key in omit_sections:
601 if title in omit_sections or key in omit_sections:
597 return
602 return
598 field = info[key]
603 field = info[key]
599 if field is not None:
604 if field is not None:
600 formatted_field = self._mime_format(field, formatter)
605 formatted_field = self._mime_format(field, formatter)
601 bundle['text/plain'].append((title, formatted_field['text/plain']))
606 bundle['text/plain'].append((title, formatted_field['text/plain']))
602 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
607 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
603
608
604 def code_formatter(text):
609 def code_formatter(text):
605 return {
610 return {
606 'text/plain': self.format(text),
611 'text/plain': self.format(text),
607 'text/html': pylight(text)
612 'text/html': pylight(text)
608 }
613 }
609
614
610 if info['isalias']:
615 if info['isalias']:
611 append_field(_mime, 'Repr', 'string_form')
616 append_field(_mime, 'Repr', 'string_form')
612
617
613 elif info['ismagic']:
618 elif info['ismagic']:
614 if detail_level > 0:
619 if detail_level > 0:
615 append_field(_mime, 'Source', 'source', code_formatter)
620 append_field(_mime, 'Source', 'source', code_formatter)
616 else:
621 else:
617 append_field(_mime, 'Docstring', 'docstring', formatter)
622 append_field(_mime, 'Docstring', 'docstring', formatter)
618 append_field(_mime, 'File', 'file')
623 append_field(_mime, 'File', 'file')
619
624
620 elif info['isclass'] or is_simple_callable(obj):
625 elif info['isclass'] or is_simple_callable(obj):
621 # Functions, methods, classes
626 # Functions, methods, classes
622 append_field(_mime, 'Signature', 'definition', code_formatter)
627 append_field(_mime, 'Signature', 'definition', code_formatter)
623 append_field(_mime, 'Init signature', 'init_definition', code_formatter)
628 append_field(_mime, 'Init signature', 'init_definition', code_formatter)
624 append_field(_mime, 'Docstring', 'docstring', formatter)
629 append_field(_mime, 'Docstring', 'docstring', formatter)
625 if detail_level > 0 and info['source']:
630 if detail_level > 0 and info['source']:
626 append_field(_mime, 'Source', 'source', code_formatter)
631 append_field(_mime, 'Source', 'source', code_formatter)
627 else:
632 else:
628 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
633 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
629
634
630 append_field(_mime, 'File', 'file')
635 append_field(_mime, 'File', 'file')
631 append_field(_mime, 'Type', 'type_name')
636 append_field(_mime, 'Type', 'type_name')
632 append_field(_mime, 'Subclasses', 'subclasses')
637 append_field(_mime, 'Subclasses', 'subclasses')
633
638
634 else:
639 else:
635 # General Python objects
640 # General Python objects
636 append_field(_mime, 'Signature', 'definition', code_formatter)
641 append_field(_mime, 'Signature', 'definition', code_formatter)
637 append_field(_mime, 'Call signature', 'call_def', code_formatter)
642 append_field(_mime, 'Call signature', 'call_def', code_formatter)
638 append_field(_mime, 'Type', 'type_name')
643 append_field(_mime, 'Type', 'type_name')
639 append_field(_mime, 'String form', 'string_form')
644 append_field(_mime, 'String form', 'string_form')
640
645
641 # Namespace
646 # Namespace
642 if info['namespace'] != 'Interactive':
647 if info['namespace'] != 'Interactive':
643 append_field(_mime, 'Namespace', 'namespace')
648 append_field(_mime, 'Namespace', 'namespace')
644
649
645 append_field(_mime, 'Length', 'length')
650 append_field(_mime, 'Length', 'length')
646 append_field(_mime, 'File', 'file')
651 append_field(_mime, 'File', 'file')
647
652
648 # Source or docstring, depending on detail level and whether
653 # Source or docstring, depending on detail level and whether
649 # source found.
654 # source found.
650 if detail_level > 0 and info['source']:
655 if detail_level > 0 and info['source']:
651 append_field(_mime, 'Source', 'source', code_formatter)
656 append_field(_mime, 'Source', 'source', code_formatter)
652 else:
657 else:
653 append_field(_mime, 'Docstring', 'docstring', formatter)
658 append_field(_mime, 'Docstring', 'docstring', formatter)
654
659
655 append_field(_mime, 'Class docstring', 'class_docstring', formatter)
660 append_field(_mime, 'Class docstring', 'class_docstring', formatter)
656 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
661 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
657 append_field(_mime, 'Call docstring', 'call_docstring', formatter)
662 append_field(_mime, 'Call docstring', 'call_docstring', formatter)
658
663
659
664
660 return self.format_mime(_mime)
665 return self.format_mime(_mime)
661
666
662 def pinfo(
667 def pinfo(
663 self,
668 self,
664 obj,
669 obj,
665 oname="",
670 oname="",
666 formatter=None,
671 formatter=None,
667 info=None,
672 info=None,
668 detail_level=0,
673 detail_level=0,
669 enable_html_pager=True,
674 enable_html_pager=True,
670 omit_sections=(),
675 omit_sections=(),
671 ):
676 ):
672 """Show detailed information about an object.
677 """Show detailed information about an object.
673
678
674 Optional arguments:
679 Optional arguments:
675
680
676 - oname: name of the variable pointing to the object.
681 - oname: name of the variable pointing to the object.
677
682
678 - formatter: callable (optional)
683 - formatter: callable (optional)
679 A special formatter for docstrings.
684 A special formatter for docstrings.
680
685
681 The formatter is a callable that takes a string as an input
686 The formatter is a callable that takes a string as an input
682 and returns either a formatted string or a mime type bundle
687 and returns either a formatted string or a mime type bundle
683 in the form of a dictionary.
688 in the form of a dictionary.
684
689
685 Although the support of custom formatter returning a string
690 Although the support of custom formatter returning a string
686 instead of a mime type bundle is deprecated.
691 instead of a mime type bundle is deprecated.
687
692
688 - info: a structure with some information fields which may have been
693 - info: a structure with some information fields which may have been
689 precomputed already.
694 precomputed already.
690
695
691 - detail_level: if set to 1, more information is given.
696 - detail_level: if set to 1, more information is given.
692
697
693 - omit_sections: set of section keys and titles to omit
698 - omit_sections: set of section keys and titles to omit
694 """
699 """
695 info = self._get_info(
700 info = self._get_info(
696 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
701 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
697 )
702 )
698 if not enable_html_pager:
703 if not enable_html_pager:
699 del info['text/html']
704 del info['text/html']
700 page.page(info)
705 page.page(info)
701
706
702 def info(self, obj, oname="", formatter=None, info=None, detail_level=0):
707 def info(self, obj, oname="", formatter=None, info=None, detail_level=0):
703 """DEPRECATED since 5.0. Compute a dict with detailed information about an object."""
708 """DEPRECATED since 5.0. Compute a dict with detailed information about an object."""
704 if formatter is not None:
709 if formatter is not None:
705 warnings.warn('The `formatter` keyword argument to `Inspector.info`'
710 warnings.warn('The `formatter` keyword argument to `Inspector.info`'
706 'is deprecated as of IPython 5.0 and will have no effects.',
711 'is deprecated as of IPython 5.0 and will have no effects.',
707 DeprecationWarning, stacklevel=2)
712 DeprecationWarning, stacklevel=2)
708 return self._info(obj, oname=oname, info=info, detail_level=detail_level)
713 return self._info(obj, oname=oname, info=info, detail_level=detail_level)
709
714
710 def _info(self, obj, oname='', info=None, detail_level=0) -> dict:
715 def _info(self, obj, oname='', info=None, detail_level=0) -> dict:
711 """Compute a dict with detailed information about an object.
716 """Compute a dict with detailed information about an object.
712
717
713 Parameters
718 Parameters
714 ==========
719 ==========
715
720
716 obj: any
721 obj: any
717 An object to find information about
722 An object to find information about
718 oname: str (default: ''):
723 oname: str (default: ''):
719 Name of the variable pointing to `obj`.
724 Name of the variable pointing to `obj`.
720 info: (default: None)
725 info: (default: None)
721 A struct (dict like with attr access) with some information fields
726 A struct (dict like with attr access) with some information fields
722 which may have been precomputed already.
727 which may have been precomputed already.
723 detail_level: int (default:0)
728 detail_level: int (default:0)
724 If set to 1, more information is given.
729 If set to 1, more information is given.
725
730
726 Returns
731 Returns
727 =======
732 =======
728
733
729 An object info dict with known fields from `info_fields`. Keys are
734 An object info dict with known fields from `info_fields`. Keys are
730 strings, values are string or None.
735 strings, values are string or None.
731 """
736 """
732
737
733 if info is None:
738 if info is None:
734 ismagic = False
739 ismagic = False
735 isalias = False
740 isalias = False
736 ospace = ''
741 ospace = ''
737 else:
742 else:
738 ismagic = info.ismagic
743 ismagic = info.ismagic
739 isalias = info.isalias
744 isalias = info.isalias
740 ospace = info.namespace
745 ospace = info.namespace
741
746
742 # Get docstring, special-casing aliases:
747 # Get docstring, special-casing aliases:
743 if isalias:
748 if isalias:
744 if not callable(obj):
749 if not callable(obj):
745 try:
750 try:
746 ds = "Alias to the system command:\n %s" % obj[1]
751 ds = "Alias to the system command:\n %s" % obj[1]
747 except:
752 except:
748 ds = "Alias: " + str(obj)
753 ds = "Alias: " + str(obj)
749 else:
754 else:
750 ds = "Alias to " + str(obj)
755 ds = "Alias to " + str(obj)
751 if obj.__doc__:
756 if obj.__doc__:
752 ds += "\nDocstring:\n" + obj.__doc__
757 ds += "\nDocstring:\n" + obj.__doc__
753 else:
758 else:
754 ds = getdoc(obj)
759 ds = getdoc(obj)
755 if ds is None:
760 if ds is None:
756 ds = '<no docstring>'
761 ds = '<no docstring>'
757
762
758 # store output in a dict, we initialize it here and fill it as we go
763 # store output in a dict, we initialize it here and fill it as we go
759 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
764 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
760
765
761 string_max = 200 # max size of strings to show (snipped if longer)
766 string_max = 200 # max size of strings to show (snipped if longer)
762 shalf = int((string_max - 5) / 2)
767 shalf = int((string_max - 5) / 2)
763
768
764 if ismagic:
769 if ismagic:
765 out['type_name'] = 'Magic function'
770 out['type_name'] = 'Magic function'
766 elif isalias:
771 elif isalias:
767 out['type_name'] = 'System alias'
772 out['type_name'] = 'System alias'
768 else:
773 else:
769 out['type_name'] = type(obj).__name__
774 out['type_name'] = type(obj).__name__
770
775
771 try:
776 try:
772 bclass = obj.__class__
777 bclass = obj.__class__
773 out['base_class'] = str(bclass)
778 out['base_class'] = str(bclass)
774 except:
779 except:
775 pass
780 pass
776
781
777 # String form, but snip if too long in ? form (full in ??)
782 # String form, but snip if too long in ? form (full in ??)
778 if detail_level >= self.str_detail_level:
783 if detail_level >= self.str_detail_level:
779 try:
784 try:
780 ostr = str(obj)
785 ostr = str(obj)
781 str_head = 'string_form'
786 str_head = 'string_form'
782 if not detail_level and len(ostr)>string_max:
787 if not detail_level and len(ostr)>string_max:
783 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
788 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
784 ostr = ("\n" + " " * len(str_head.expandtabs())).\
789 ostr = ("\n" + " " * len(str_head.expandtabs())).\
785 join(q.strip() for q in ostr.split("\n"))
790 join(q.strip() for q in ostr.split("\n"))
786 out[str_head] = ostr
791 out[str_head] = ostr
787 except:
792 except:
788 pass
793 pass
789
794
790 if ospace:
795 if ospace:
791 out['namespace'] = ospace
796 out['namespace'] = ospace
792
797
793 # Length (for strings and lists)
798 # Length (for strings and lists)
794 try:
799 try:
795 out['length'] = str(len(obj))
800 out['length'] = str(len(obj))
796 except Exception:
801 except Exception:
797 pass
802 pass
798
803
799 # Filename where object was defined
804 # Filename where object was defined
800 binary_file = False
805 binary_file = False
801 fname = find_file(obj)
806 fname = find_file(obj)
802 if fname is None:
807 if fname is None:
803 # if anything goes wrong, we don't want to show source, so it's as
808 # if anything goes wrong, we don't want to show source, so it's as
804 # if the file was binary
809 # if the file was binary
805 binary_file = True
810 binary_file = True
806 else:
811 else:
807 if fname.endswith(('.so', '.dll', '.pyd')):
812 if fname.endswith(('.so', '.dll', '.pyd')):
808 binary_file = True
813 binary_file = True
809 elif fname.endswith('<string>'):
814 elif fname.endswith('<string>'):
810 fname = 'Dynamically generated function. No source code available.'
815 fname = 'Dynamically generated function. No source code available.'
811 out['file'] = compress_user(fname)
816 out['file'] = compress_user(fname)
812
817
813 # Original source code for a callable, class or property.
818 # Original source code for a callable, class or property.
814 if detail_level:
819 if detail_level:
815 # Flush the source cache because inspect can return out-of-date
820 # Flush the source cache because inspect can return out-of-date
816 # source
821 # source
817 linecache.checkcache()
822 linecache.checkcache()
818 try:
823 try:
819 if isinstance(obj, property) or not binary_file:
824 if isinstance(obj, property) or not binary_file:
820 src = getsource(obj, oname)
825 src = getsource(obj, oname)
821 if src is not None:
826 if src is not None:
822 src = src.rstrip()
827 src = src.rstrip()
823 out['source'] = src
828 out['source'] = src
824
829
825 except Exception:
830 except Exception:
826 pass
831 pass
827
832
828 # Add docstring only if no source is to be shown (avoid repetitions).
833 # Add docstring only if no source is to be shown (avoid repetitions).
829 if ds and not self._source_contains_docstring(out.get('source'), ds):
834 if ds and not self._source_contains_docstring(out.get('source'), ds):
830 out['docstring'] = ds
835 out['docstring'] = ds
831
836
832 # Constructor docstring for classes
837 # Constructor docstring for classes
833 if inspect.isclass(obj):
838 if inspect.isclass(obj):
834 out['isclass'] = True
839 out['isclass'] = True
835
840
836 # get the init signature:
841 # get the init signature:
837 try:
842 try:
838 init_def = self._getdef(obj, oname)
843 init_def = self._getdef(obj, oname)
839 except AttributeError:
844 except AttributeError:
840 init_def = None
845 init_def = None
841
846
842 # get the __init__ docstring
847 # get the __init__ docstring
843 try:
848 try:
844 obj_init = obj.__init__
849 obj_init = obj.__init__
845 except AttributeError:
850 except AttributeError:
846 init_ds = None
851 init_ds = None
847 else:
852 else:
848 if init_def is None:
853 if init_def is None:
849 # Get signature from init if top-level sig failed.
854 # Get signature from init if top-level sig failed.
850 # Can happen for built-in types (list, etc.).
855 # Can happen for built-in types (list, etc.).
851 try:
856 try:
852 init_def = self._getdef(obj_init, oname)
857 init_def = self._getdef(obj_init, oname)
853 except AttributeError:
858 except AttributeError:
854 pass
859 pass
855 init_ds = getdoc(obj_init)
860 init_ds = getdoc(obj_init)
856 # Skip Python's auto-generated docstrings
861 # Skip Python's auto-generated docstrings
857 if init_ds == _object_init_docstring:
862 if init_ds == _object_init_docstring:
858 init_ds = None
863 init_ds = None
859
864
860 if init_def:
865 if init_def:
861 out['init_definition'] = init_def
866 out['init_definition'] = init_def
862
867
863 if init_ds:
868 if init_ds:
864 out['init_docstring'] = init_ds
869 out['init_docstring'] = init_ds
865
870
866 names = [sub.__name__ for sub in type.__subclasses__(obj)]
871 names = [sub.__name__ for sub in type.__subclasses__(obj)]
867 if len(names) < 10:
872 if len(names) < 10:
868 all_names = ', '.join(names)
873 all_names = ', '.join(names)
869 else:
874 else:
870 all_names = ', '.join(names[:10]+['...'])
875 all_names = ', '.join(names[:10]+['...'])
871 out['subclasses'] = all_names
876 out['subclasses'] = all_names
872 # and class docstring for instances:
877 # and class docstring for instances:
873 else:
878 else:
874 # reconstruct the function definition and print it:
879 # reconstruct the function definition and print it:
875 defln = self._getdef(obj, oname)
880 defln = self._getdef(obj, oname)
876 if defln:
881 if defln:
877 out['definition'] = defln
882 out['definition'] = defln
878
883
879 # First, check whether the instance docstring is identical to the
884 # First, check whether the instance docstring is identical to the
880 # class one, and print it separately if they don't coincide. In
885 # class one, and print it separately if they don't coincide. In
881 # most cases they will, but it's nice to print all the info for
886 # most cases they will, but it's nice to print all the info for
882 # objects which use instance-customized docstrings.
887 # objects which use instance-customized docstrings.
883 if ds:
888 if ds:
884 try:
889 try:
885 cls = getattr(obj,'__class__')
890 cls = getattr(obj,'__class__')
886 except:
891 except:
887 class_ds = None
892 class_ds = None
888 else:
893 else:
889 class_ds = getdoc(cls)
894 class_ds = getdoc(cls)
890 # Skip Python's auto-generated docstrings
895 # Skip Python's auto-generated docstrings
891 if class_ds in _builtin_type_docstrings:
896 if class_ds in _builtin_type_docstrings:
892 class_ds = None
897 class_ds = None
893 if class_ds and ds != class_ds:
898 if class_ds and ds != class_ds:
894 out['class_docstring'] = class_ds
899 out['class_docstring'] = class_ds
895
900
896 # Next, try to show constructor docstrings
901 # Next, try to show constructor docstrings
897 try:
902 try:
898 init_ds = getdoc(obj.__init__)
903 init_ds = getdoc(obj.__init__)
899 # Skip Python's auto-generated docstrings
904 # Skip Python's auto-generated docstrings
900 if init_ds == _object_init_docstring:
905 if init_ds == _object_init_docstring:
901 init_ds = None
906 init_ds = None
902 except AttributeError:
907 except AttributeError:
903 init_ds = None
908 init_ds = None
904 if init_ds:
909 if init_ds:
905 out['init_docstring'] = init_ds
910 out['init_docstring'] = init_ds
906
911
907 # Call form docstring for callable instances
912 # Call form docstring for callable instances
908 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
913 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
909 call_def = self._getdef(obj.__call__, oname)
914 call_def = self._getdef(obj.__call__, oname)
910 if call_def and (call_def != out.get('definition')):
915 if call_def and (call_def != out.get('definition')):
911 # it may never be the case that call def and definition differ,
916 # it may never be the case that call def and definition differ,
912 # but don't include the same signature twice
917 # but don't include the same signature twice
913 out['call_def'] = call_def
918 out['call_def'] = call_def
914 call_ds = getdoc(obj.__call__)
919 call_ds = getdoc(obj.__call__)
915 # Skip Python's auto-generated docstrings
920 # Skip Python's auto-generated docstrings
916 if call_ds == _func_call_docstring:
921 if call_ds == _func_call_docstring:
917 call_ds = None
922 call_ds = None
918 if call_ds:
923 if call_ds:
919 out['call_docstring'] = call_ds
924 out['call_docstring'] = call_ds
920
925
921 return object_info(**out)
926 return object_info(**out)
922
927
923 @staticmethod
928 @staticmethod
924 def _source_contains_docstring(src, doc):
929 def _source_contains_docstring(src, doc):
925 """
930 """
926 Check whether the source *src* contains the docstring *doc*.
931 Check whether the source *src* contains the docstring *doc*.
927
932
928 This is is helper function to skip displaying the docstring if the
933 This is is helper function to skip displaying the docstring if the
929 source already contains it, avoiding repetition of information.
934 source already contains it, avoiding repetition of information.
930 """
935 """
931 try:
936 try:
932 def_node, = ast.parse(dedent(src)).body
937 def_node, = ast.parse(dedent(src)).body
933 return ast.get_docstring(def_node) == doc
938 return ast.get_docstring(def_node) == doc
934 except Exception:
939 except Exception:
935 # The source can become invalid or even non-existent (because it
940 # The source can become invalid or even non-existent (because it
936 # is re-fetched from the source file) so the above code fail in
941 # is re-fetched from the source file) so the above code fail in
937 # arbitrary ways.
942 # arbitrary ways.
938 return False
943 return False
939
944
940 def psearch(self,pattern,ns_table,ns_search=[],
945 def psearch(self,pattern,ns_table,ns_search=[],
941 ignore_case=False,show_all=False, *, list_types=False):
946 ignore_case=False,show_all=False, *, list_types=False):
942 """Search namespaces with wildcards for objects.
947 """Search namespaces with wildcards for objects.
943
948
944 Arguments:
949 Arguments:
945
950
946 - pattern: string containing shell-like wildcards to use in namespace
951 - pattern: string containing shell-like wildcards to use in namespace
947 searches and optionally a type specification to narrow the search to
952 searches and optionally a type specification to narrow the search to
948 objects of that type.
953 objects of that type.
949
954
950 - ns_table: dict of name->namespaces for search.
955 - ns_table: dict of name->namespaces for search.
951
956
952 Optional arguments:
957 Optional arguments:
953
958
954 - ns_search: list of namespace names to include in search.
959 - ns_search: list of namespace names to include in search.
955
960
956 - ignore_case(False): make the search case-insensitive.
961 - ignore_case(False): make the search case-insensitive.
957
962
958 - show_all(False): show all names, including those starting with
963 - show_all(False): show all names, including those starting with
959 underscores.
964 underscores.
960
965
961 - list_types(False): list all available object types for object matching.
966 - list_types(False): list all available object types for object matching.
962 """
967 """
963 #print 'ps pattern:<%r>' % pattern # dbg
968 #print 'ps pattern:<%r>' % pattern # dbg
964
969
965 # defaults
970 # defaults
966 type_pattern = 'all'
971 type_pattern = 'all'
967 filter = ''
972 filter = ''
968
973
969 # list all object types
974 # list all object types
970 if list_types:
975 if list_types:
971 page.page('\n'.join(sorted(typestr2type)))
976 page.page('\n'.join(sorted(typestr2type)))
972 return
977 return
973
978
974 cmds = pattern.split()
979 cmds = pattern.split()
975 len_cmds = len(cmds)
980 len_cmds = len(cmds)
976 if len_cmds == 1:
981 if len_cmds == 1:
977 # Only filter pattern given
982 # Only filter pattern given
978 filter = cmds[0]
983 filter = cmds[0]
979 elif len_cmds == 2:
984 elif len_cmds == 2:
980 # Both filter and type specified
985 # Both filter and type specified
981 filter,type_pattern = cmds
986 filter,type_pattern = cmds
982 else:
987 else:
983 raise ValueError('invalid argument string for psearch: <%s>' %
988 raise ValueError('invalid argument string for psearch: <%s>' %
984 pattern)
989 pattern)
985
990
986 # filter search namespaces
991 # filter search namespaces
987 for name in ns_search:
992 for name in ns_search:
988 if name not in ns_table:
993 if name not in ns_table:
989 raise ValueError('invalid namespace <%s>. Valid names: %s' %
994 raise ValueError('invalid namespace <%s>. Valid names: %s' %
990 (name,ns_table.keys()))
995 (name,ns_table.keys()))
991
996
992 #print 'type_pattern:',type_pattern # dbg
997 #print 'type_pattern:',type_pattern # dbg
993 search_result, namespaces_seen = set(), set()
998 search_result, namespaces_seen = set(), set()
994 for ns_name in ns_search:
999 for ns_name in ns_search:
995 ns = ns_table[ns_name]
1000 ns = ns_table[ns_name]
996 # Normally, locals and globals are the same, so we just check one.
1001 # Normally, locals and globals are the same, so we just check one.
997 if id(ns) in namespaces_seen:
1002 if id(ns) in namespaces_seen:
998 continue
1003 continue
999 namespaces_seen.add(id(ns))
1004 namespaces_seen.add(id(ns))
1000 tmp_res = list_namespace(ns, type_pattern, filter,
1005 tmp_res = list_namespace(ns, type_pattern, filter,
1001 ignore_case=ignore_case, show_all=show_all)
1006 ignore_case=ignore_case, show_all=show_all)
1002 search_result.update(tmp_res)
1007 search_result.update(tmp_res)
1003
1008
1004 page.page('\n'.join(sorted(search_result)))
1009 page.page('\n'.join(sorted(search_result)))
1005
1010
1006
1011
1007 def _render_signature(obj_signature, obj_name) -> str:
1012 def _render_signature(obj_signature, obj_name) -> str:
1008 """
1013 """
1009 This was mostly taken from inspect.Signature.__str__.
1014 This was mostly taken from inspect.Signature.__str__.
1010 Look there for the comments.
1015 Look there for the comments.
1011 The only change is to add linebreaks when this gets too long.
1016 The only change is to add linebreaks when this gets too long.
1012 """
1017 """
1013 result = []
1018 result = []
1014 pos_only = False
1019 pos_only = False
1015 kw_only = True
1020 kw_only = True
1016 for param in obj_signature.parameters.values():
1021 for param in obj_signature.parameters.values():
1017 if param.kind == inspect._POSITIONAL_ONLY:
1022 if param.kind == inspect._POSITIONAL_ONLY:
1018 pos_only = True
1023 pos_only = True
1019 elif pos_only:
1024 elif pos_only:
1020 result.append('/')
1025 result.append('/')
1021 pos_only = False
1026 pos_only = False
1022
1027
1023 if param.kind == inspect._VAR_POSITIONAL:
1028 if param.kind == inspect._VAR_POSITIONAL:
1024 kw_only = False
1029 kw_only = False
1025 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1030 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1026 result.append('*')
1031 result.append('*')
1027 kw_only = False
1032 kw_only = False
1028
1033
1029 result.append(str(param))
1034 result.append(str(param))
1030
1035
1031 if pos_only:
1036 if pos_only:
1032 result.append('/')
1037 result.append('/')
1033
1038
1034 # add up name, parameters, braces (2), and commas
1039 # add up name, parameters, braces (2), and commas
1035 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1040 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1036 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1041 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1037 rendered = '{}(\n{})'.format(obj_name, ''.join(
1042 rendered = '{}(\n{})'.format(obj_name, ''.join(
1038 ' {},\n'.format(r) for r in result)
1043 ' {},\n'.format(r) for r in result)
1039 )
1044 )
1040 else:
1045 else:
1041 rendered = '{}({})'.format(obj_name, ', '.join(result))
1046 rendered = '{}({})'.format(obj_name, ', '.join(result))
1042
1047
1043 if obj_signature.return_annotation is not inspect._empty:
1048 if obj_signature.return_annotation is not inspect._empty:
1044 anno = inspect.formatannotation(obj_signature.return_annotation)
1049 anno = inspect.formatannotation(obj_signature.return_annotation)
1045 rendered += ' -> {}'.format(anno)
1050 rendered += ' -> {}'.format(anno)
1046
1051
1047 return rendered
1052 return rendered
@@ -1,453 +1,476 b''
1 """Tests for the object inspection functionality.
1 """Tests for the object inspection functionality.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from inspect import signature, Signature, Parameter
8 from inspect import signature, Signature, Parameter
9 import inspect
9 import os
10 import os
11 import pytest
10 import re
12 import re
13 import sys
11
14
12 from .. import oinspect
15 from .. import oinspect
13
16
14 from decorator import decorator
17 from decorator import decorator
15
18
16 from IPython.testing.tools import AssertPrints, AssertNotPrints
19 from IPython.testing.tools import AssertPrints, AssertNotPrints
17 from IPython.utils.path import compress_user
20 from IPython.utils.path import compress_user
18
21
19
22
20 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
21 # Globals and constants
24 # Globals and constants
22 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
23
26
24 inspector = None
27 inspector = None
25
28
26 def setup_module():
29 def setup_module():
27 global inspector
30 global inspector
28 inspector = oinspect.Inspector()
31 inspector = oinspect.Inspector()
29
32
30
33
34 class SourceModuleMainTest:
35 __module__ = "__main__"
36
37
31 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
32 # Local utilities
39 # Local utilities
33 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
34
41
35 # WARNING: since this test checks the line number where a function is
42 # WARNING: since this test checks the line number where a function is
36 # defined, if any code is inserted above, the following line will need to be
43 # defined, if any code is inserted above, the following line will need to be
37 # updated. Do NOT insert any whitespace between the next line and the function
44 # updated. Do NOT insert any whitespace between the next line and the function
38 # definition below.
45 # definition below.
39 THIS_LINE_NUMBER = 39 # Put here the actual number of this line
46 THIS_LINE_NUMBER = 46 # Put here the actual number of this line
47
48
49 def test_find_source_lines():
50 assert oinspect.find_source_lines(test_find_source_lines) == THIS_LINE_NUMBER + 3
51 assert oinspect.find_source_lines(type) is None
52 assert oinspect.find_source_lines(SourceModuleMainTest) is None
53 assert oinspect.find_source_lines(SourceModuleMainTest()) is None
54
40
55
41 from unittest import TestCase
56 def test_getsource():
57 assert oinspect.getsource(type) is None
58 assert oinspect.getsource(SourceModuleMainTest) is None
59 assert oinspect.getsource(SourceModuleMainTest()) is None
42
60
43 class Test(TestCase):
44
61
45 def test_find_source_lines(self):
62 def test_inspect_getfile_raises_exception():
46 self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
63 """Check oinspect.find_file/getsource/find_source_lines expectations"""
47 THIS_LINE_NUMBER+6)
64 with pytest.raises(TypeError):
65 inspect.getfile(type)
66 with pytest.raises(OSError if sys.version_info >= (3, 10) else TypeError):
67 inspect.getfile(SourceModuleMainTest)
48
68
49
69
50 # A couple of utilities to ensure these tests work the same from a source or a
70 # A couple of utilities to ensure these tests work the same from a source or a
51 # binary install
71 # binary install
52 def pyfile(fname):
72 def pyfile(fname):
53 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
73 return os.path.normcase(re.sub('.py[co]$', '.py', fname))
54
74
55
75
56 def match_pyfiles(f1, f2):
76 def match_pyfiles(f1, f2):
57 assert pyfile(f1) == pyfile(f2)
77 assert pyfile(f1) == pyfile(f2)
58
78
59
79
60 def test_find_file():
80 def test_find_file():
61 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
81 match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
82 assert oinspect.find_file(type) is None
83 assert oinspect.find_file(SourceModuleMainTest) is None
84 assert oinspect.find_file(SourceModuleMainTest()) is None
62
85
63
86
64 def test_find_file_decorated1():
87 def test_find_file_decorated1():
65
88
66 @decorator
89 @decorator
67 def noop1(f):
90 def noop1(f):
68 def wrapper(*a, **kw):
91 def wrapper(*a, **kw):
69 return f(*a, **kw)
92 return f(*a, **kw)
70 return wrapper
93 return wrapper
71
94
72 @noop1
95 @noop1
73 def f(x):
96 def f(x):
74 "My docstring"
97 "My docstring"
75
98
76 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
99 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
77 assert f.__doc__ == "My docstring"
100 assert f.__doc__ == "My docstring"
78
101
79
102
80 def test_find_file_decorated2():
103 def test_find_file_decorated2():
81
104
82 @decorator
105 @decorator
83 def noop2(f, *a, **kw):
106 def noop2(f, *a, **kw):
84 return f(*a, **kw)
107 return f(*a, **kw)
85
108
86 @noop2
109 @noop2
87 @noop2
110 @noop2
88 @noop2
111 @noop2
89 def f(x):
112 def f(x):
90 "My docstring 2"
113 "My docstring 2"
91
114
92 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
115 match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
93 assert f.__doc__ == "My docstring 2"
116 assert f.__doc__ == "My docstring 2"
94
117
95
118
96 def test_find_file_magic():
119 def test_find_file_magic():
97 run = ip.find_line_magic('run')
120 run = ip.find_line_magic('run')
98 assert oinspect.find_file(run) is not None
121 assert oinspect.find_file(run) is not None
99
122
100
123
101 # A few generic objects we can then inspect in the tests below
124 # A few generic objects we can then inspect in the tests below
102
125
103 class Call(object):
126 class Call(object):
104 """This is the class docstring."""
127 """This is the class docstring."""
105
128
106 def __init__(self, x, y=1):
129 def __init__(self, x, y=1):
107 """This is the constructor docstring."""
130 """This is the constructor docstring."""
108
131
109 def __call__(self, *a, **kw):
132 def __call__(self, *a, **kw):
110 """This is the call docstring."""
133 """This is the call docstring."""
111
134
112 def method(self, x, z=2):
135 def method(self, x, z=2):
113 """Some method's docstring"""
136 """Some method's docstring"""
114
137
115 class HasSignature(object):
138 class HasSignature(object):
116 """This is the class docstring."""
139 """This is the class docstring."""
117 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
140 __signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
118
141
119 def __init__(self, *args):
142 def __init__(self, *args):
120 """This is the init docstring"""
143 """This is the init docstring"""
121
144
122
145
123 class SimpleClass(object):
146 class SimpleClass(object):
124 def method(self, x, z=2):
147 def method(self, x, z=2):
125 """Some method's docstring"""
148 """Some method's docstring"""
126
149
127
150
128 class Awkward(object):
151 class Awkward(object):
129 def __getattr__(self, name):
152 def __getattr__(self, name):
130 raise Exception(name)
153 raise Exception(name)
131
154
132 class NoBoolCall:
155 class NoBoolCall:
133 """
156 """
134 callable with `__bool__` raising should still be inspect-able.
157 callable with `__bool__` raising should still be inspect-able.
135 """
158 """
136
159
137 def __call__(self):
160 def __call__(self):
138 """does nothing"""
161 """does nothing"""
139 pass
162 pass
140
163
141 def __bool__(self):
164 def __bool__(self):
142 """just raise NotImplemented"""
165 """just raise NotImplemented"""
143 raise NotImplementedError('Must be implemented')
166 raise NotImplementedError('Must be implemented')
144
167
145
168
146 class SerialLiar(object):
169 class SerialLiar(object):
147 """Attribute accesses always get another copy of the same class.
170 """Attribute accesses always get another copy of the same class.
148
171
149 unittest.mock.call does something similar, but it's not ideal for testing
172 unittest.mock.call does something similar, but it's not ideal for testing
150 as the failure mode is to eat all your RAM. This gives up after 10k levels.
173 as the failure mode is to eat all your RAM. This gives up after 10k levels.
151 """
174 """
152 def __init__(self, max_fibbing_twig, lies_told=0):
175 def __init__(self, max_fibbing_twig, lies_told=0):
153 if lies_told > 10000:
176 if lies_told > 10000:
154 raise RuntimeError('Nose too long, honesty is the best policy')
177 raise RuntimeError('Nose too long, honesty is the best policy')
155 self.max_fibbing_twig = max_fibbing_twig
178 self.max_fibbing_twig = max_fibbing_twig
156 self.lies_told = lies_told
179 self.lies_told = lies_told
157 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
180 max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
158
181
159 def __getattr__(self, item):
182 def __getattr__(self, item):
160 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
183 return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
161
184
162 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
163 # Tests
186 # Tests
164 #-----------------------------------------------------------------------------
187 #-----------------------------------------------------------------------------
165
188
166 def test_info():
189 def test_info():
167 "Check that Inspector.info fills out various fields as expected."
190 "Check that Inspector.info fills out various fields as expected."
168 i = inspector.info(Call, oname="Call")
191 i = inspector.info(Call, oname="Call")
169 assert i["type_name"] == "type"
192 assert i["type_name"] == "type"
170 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
193 expected_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
171 assert i["base_class"] == expected_class
194 assert i["base_class"] == expected_class
172 assert re.search(
195 assert re.search(
173 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
196 "<class 'IPython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>",
174 i["string_form"],
197 i["string_form"],
175 )
198 )
176 fname = __file__
199 fname = __file__
177 if fname.endswith(".pyc"):
200 if fname.endswith(".pyc"):
178 fname = fname[:-1]
201 fname = fname[:-1]
179 # case-insensitive comparison needed on some filesystems
202 # case-insensitive comparison needed on some filesystems
180 # e.g. Windows:
203 # e.g. Windows:
181 assert i["file"].lower() == compress_user(fname).lower()
204 assert i["file"].lower() == compress_user(fname).lower()
182 assert i["definition"] == None
205 assert i["definition"] == None
183 assert i["docstring"] == Call.__doc__
206 assert i["docstring"] == Call.__doc__
184 assert i["source"] == None
207 assert i["source"] == None
185 assert i["isclass"] is True
208 assert i["isclass"] is True
186 assert i["init_definition"] == "Call(x, y=1)"
209 assert i["init_definition"] == "Call(x, y=1)"
187 assert i["init_docstring"] == Call.__init__.__doc__
210 assert i["init_docstring"] == Call.__init__.__doc__
188
211
189 i = inspector.info(Call, detail_level=1)
212 i = inspector.info(Call, detail_level=1)
190 assert i["source"] is not None
213 assert i["source"] is not None
191 assert i["docstring"] == None
214 assert i["docstring"] == None
192
215
193 c = Call(1)
216 c = Call(1)
194 c.__doc__ = "Modified instance docstring"
217 c.__doc__ = "Modified instance docstring"
195 i = inspector.info(c)
218 i = inspector.info(c)
196 assert i["type_name"] == "Call"
219 assert i["type_name"] == "Call"
197 assert i["docstring"] == "Modified instance docstring"
220 assert i["docstring"] == "Modified instance docstring"
198 assert i["class_docstring"] == Call.__doc__
221 assert i["class_docstring"] == Call.__doc__
199 assert i["init_docstring"] == Call.__init__.__doc__
222 assert i["init_docstring"] == Call.__init__.__doc__
200 assert i["call_docstring"] == Call.__call__.__doc__
223 assert i["call_docstring"] == Call.__call__.__doc__
201
224
202
225
203 def test_class_signature():
226 def test_class_signature():
204 info = inspector.info(HasSignature, "HasSignature")
227 info = inspector.info(HasSignature, "HasSignature")
205 assert info["init_definition"] == "HasSignature(test)"
228 assert info["init_definition"] == "HasSignature(test)"
206 assert info["init_docstring"] == HasSignature.__init__.__doc__
229 assert info["init_docstring"] == HasSignature.__init__.__doc__
207
230
208
231
209 def test_info_awkward():
232 def test_info_awkward():
210 # Just test that this doesn't throw an error.
233 # Just test that this doesn't throw an error.
211 inspector.info(Awkward())
234 inspector.info(Awkward())
212
235
213 def test_bool_raise():
236 def test_bool_raise():
214 inspector.info(NoBoolCall())
237 inspector.info(NoBoolCall())
215
238
216 def test_info_serialliar():
239 def test_info_serialliar():
217 fib_tracker = [0]
240 fib_tracker = [0]
218 inspector.info(SerialLiar(fib_tracker))
241 inspector.info(SerialLiar(fib_tracker))
219
242
220 # Nested attribute access should be cut off at 100 levels deep to avoid
243 # Nested attribute access should be cut off at 100 levels deep to avoid
221 # infinite loops: https://github.com/ipython/ipython/issues/9122
244 # infinite loops: https://github.com/ipython/ipython/issues/9122
222 assert fib_tracker[0] < 9000
245 assert fib_tracker[0] < 9000
223
246
224 def support_function_one(x, y=2, *a, **kw):
247 def support_function_one(x, y=2, *a, **kw):
225 """A simple function."""
248 """A simple function."""
226
249
227 def test_calldef_none():
250 def test_calldef_none():
228 # We should ignore __call__ for all of these.
251 # We should ignore __call__ for all of these.
229 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
252 for obj in [support_function_one, SimpleClass().method, any, str.upper]:
230 i = inspector.info(obj)
253 i = inspector.info(obj)
231 assert i["call_def"] is None
254 assert i["call_def"] is None
232
255
233
256
234 def f_kwarg(pos, *, kwonly):
257 def f_kwarg(pos, *, kwonly):
235 pass
258 pass
236
259
237 def test_definition_kwonlyargs():
260 def test_definition_kwonlyargs():
238 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
261 i = inspector.info(f_kwarg, oname="f_kwarg") # analysis:ignore
239 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
262 assert i["definition"] == "f_kwarg(pos, *, kwonly)"
240
263
241
264
242 def test_getdoc():
265 def test_getdoc():
243 class A(object):
266 class A(object):
244 """standard docstring"""
267 """standard docstring"""
245 pass
268 pass
246
269
247 class B(object):
270 class B(object):
248 """standard docstring"""
271 """standard docstring"""
249 def getdoc(self):
272 def getdoc(self):
250 return "custom docstring"
273 return "custom docstring"
251
274
252 class C(object):
275 class C(object):
253 """standard docstring"""
276 """standard docstring"""
254 def getdoc(self):
277 def getdoc(self):
255 return None
278 return None
256
279
257 a = A()
280 a = A()
258 b = B()
281 b = B()
259 c = C()
282 c = C()
260
283
261 assert oinspect.getdoc(a) == "standard docstring"
284 assert oinspect.getdoc(a) == "standard docstring"
262 assert oinspect.getdoc(b) == "custom docstring"
285 assert oinspect.getdoc(b) == "custom docstring"
263 assert oinspect.getdoc(c) == "standard docstring"
286 assert oinspect.getdoc(c) == "standard docstring"
264
287
265
288
266 def test_empty_property_has_no_source():
289 def test_empty_property_has_no_source():
267 i = inspector.info(property(), detail_level=1)
290 i = inspector.info(property(), detail_level=1)
268 assert i["source"] is None
291 assert i["source"] is None
269
292
270
293
271 def test_property_sources():
294 def test_property_sources():
272 # A simple adder whose source and signature stays
295 # A simple adder whose source and signature stays
273 # the same across Python distributions
296 # the same across Python distributions
274 def simple_add(a, b):
297 def simple_add(a, b):
275 "Adds two numbers"
298 "Adds two numbers"
276 return a + b
299 return a + b
277
300
278 class A(object):
301 class A(object):
279 @property
302 @property
280 def foo(self):
303 def foo(self):
281 return 'bar'
304 return 'bar'
282
305
283 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
306 foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
284
307
285 dname = property(oinspect.getdoc)
308 dname = property(oinspect.getdoc)
286 adder = property(simple_add)
309 adder = property(simple_add)
287
310
288 i = inspector.info(A.foo, detail_level=1)
311 i = inspector.info(A.foo, detail_level=1)
289 assert "def foo(self):" in i["source"]
312 assert "def foo(self):" in i["source"]
290 assert "lambda self, v:" in i["source"]
313 assert "lambda self, v:" in i["source"]
291
314
292 i = inspector.info(A.dname, detail_level=1)
315 i = inspector.info(A.dname, detail_level=1)
293 assert "def getdoc(obj)" in i["source"]
316 assert "def getdoc(obj)" in i["source"]
294
317
295 i = inspector.info(A.adder, detail_level=1)
318 i = inspector.info(A.adder, detail_level=1)
296 assert "def simple_add(a, b)" in i["source"]
319 assert "def simple_add(a, b)" in i["source"]
297
320
298
321
299 def test_property_docstring_is_in_info_for_detail_level_0():
322 def test_property_docstring_is_in_info_for_detail_level_0():
300 class A(object):
323 class A(object):
301 @property
324 @property
302 def foobar(self):
325 def foobar(self):
303 """This is `foobar` property."""
326 """This is `foobar` property."""
304 pass
327 pass
305
328
306 ip.user_ns["a_obj"] = A()
329 ip.user_ns["a_obj"] = A()
307 assert (
330 assert (
308 "This is `foobar` property."
331 "This is `foobar` property."
309 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
332 == ip.object_inspect("a_obj.foobar", detail_level=0)["docstring"]
310 )
333 )
311
334
312 ip.user_ns["a_cls"] = A
335 ip.user_ns["a_cls"] = A
313 assert (
336 assert (
314 "This is `foobar` property."
337 "This is `foobar` property."
315 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
338 == ip.object_inspect("a_cls.foobar", detail_level=0)["docstring"]
316 )
339 )
317
340
318
341
319 def test_pdef():
342 def test_pdef():
320 # See gh-1914
343 # See gh-1914
321 def foo(): pass
344 def foo(): pass
322 inspector.pdef(foo, 'foo')
345 inspector.pdef(foo, 'foo')
323
346
324
347
325 def test_pinfo_nonascii():
348 def test_pinfo_nonascii():
326 # See gh-1177
349 # See gh-1177
327 from . import nonascii2
350 from . import nonascii2
328 ip.user_ns['nonascii2'] = nonascii2
351 ip.user_ns['nonascii2'] = nonascii2
329 ip._inspect('pinfo', 'nonascii2', detail_level=1)
352 ip._inspect('pinfo', 'nonascii2', detail_level=1)
330
353
331 def test_pinfo_type():
354 def test_pinfo_type():
332 """
355 """
333 type can fail in various edge case, for example `type.__subclass__()`
356 type can fail in various edge case, for example `type.__subclass__()`
334 """
357 """
335 ip._inspect('pinfo', 'type')
358 ip._inspect('pinfo', 'type')
336
359
337
360
338 def test_pinfo_docstring_no_source():
361 def test_pinfo_docstring_no_source():
339 """Docstring should be included with detail_level=1 if there is no source"""
362 """Docstring should be included with detail_level=1 if there is no source"""
340 with AssertPrints('Docstring:'):
363 with AssertPrints('Docstring:'):
341 ip._inspect('pinfo', 'str.format', detail_level=0)
364 ip._inspect('pinfo', 'str.format', detail_level=0)
342 with AssertPrints('Docstring:'):
365 with AssertPrints('Docstring:'):
343 ip._inspect('pinfo', 'str.format', detail_level=1)
366 ip._inspect('pinfo', 'str.format', detail_level=1)
344
367
345
368
346 def test_pinfo_no_docstring_if_source():
369 def test_pinfo_no_docstring_if_source():
347 """Docstring should not be included with detail_level=1 if source is found"""
370 """Docstring should not be included with detail_level=1 if source is found"""
348 def foo():
371 def foo():
349 """foo has a docstring"""
372 """foo has a docstring"""
350
373
351 ip.user_ns['foo'] = foo
374 ip.user_ns['foo'] = foo
352
375
353 with AssertPrints('Docstring:'):
376 with AssertPrints('Docstring:'):
354 ip._inspect('pinfo', 'foo', detail_level=0)
377 ip._inspect('pinfo', 'foo', detail_level=0)
355 with AssertPrints('Source:'):
378 with AssertPrints('Source:'):
356 ip._inspect('pinfo', 'foo', detail_level=1)
379 ip._inspect('pinfo', 'foo', detail_level=1)
357 with AssertNotPrints('Docstring:'):
380 with AssertNotPrints('Docstring:'):
358 ip._inspect('pinfo', 'foo', detail_level=1)
381 ip._inspect('pinfo', 'foo', detail_level=1)
359
382
360
383
361 def test_pinfo_docstring_if_detail_and_no_source():
384 def test_pinfo_docstring_if_detail_and_no_source():
362 """ Docstring should be displayed if source info not available """
385 """ Docstring should be displayed if source info not available """
363 obj_def = '''class Foo(object):
386 obj_def = '''class Foo(object):
364 """ This is a docstring for Foo """
387 """ This is a docstring for Foo """
365 def bar(self):
388 def bar(self):
366 """ This is a docstring for Foo.bar """
389 """ This is a docstring for Foo.bar """
367 pass
390 pass
368 '''
391 '''
369
392
370 ip.run_cell(obj_def)
393 ip.run_cell(obj_def)
371 ip.run_cell('foo = Foo()')
394 ip.run_cell('foo = Foo()')
372
395
373 with AssertNotPrints("Source:"):
396 with AssertNotPrints("Source:"):
374 with AssertPrints('Docstring:'):
397 with AssertPrints('Docstring:'):
375 ip._inspect('pinfo', 'foo', detail_level=0)
398 ip._inspect('pinfo', 'foo', detail_level=0)
376 with AssertPrints('Docstring:'):
399 with AssertPrints('Docstring:'):
377 ip._inspect('pinfo', 'foo', detail_level=1)
400 ip._inspect('pinfo', 'foo', detail_level=1)
378 with AssertPrints('Docstring:'):
401 with AssertPrints('Docstring:'):
379 ip._inspect('pinfo', 'foo.bar', detail_level=0)
402 ip._inspect('pinfo', 'foo.bar', detail_level=0)
380
403
381 with AssertNotPrints('Docstring:'):
404 with AssertNotPrints('Docstring:'):
382 with AssertPrints('Source:'):
405 with AssertPrints('Source:'):
383 ip._inspect('pinfo', 'foo.bar', detail_level=1)
406 ip._inspect('pinfo', 'foo.bar', detail_level=1)
384
407
385
408
386 def test_pinfo_magic():
409 def test_pinfo_magic():
387 with AssertPrints('Docstring:'):
410 with AssertPrints('Docstring:'):
388 ip._inspect('pinfo', 'lsmagic', detail_level=0)
411 ip._inspect('pinfo', 'lsmagic', detail_level=0)
389
412
390 with AssertPrints('Source:'):
413 with AssertPrints('Source:'):
391 ip._inspect('pinfo', 'lsmagic', detail_level=1)
414 ip._inspect('pinfo', 'lsmagic', detail_level=1)
392
415
393
416
394 def test_init_colors():
417 def test_init_colors():
395 # ensure colors are not present in signature info
418 # ensure colors are not present in signature info
396 info = inspector.info(HasSignature)
419 info = inspector.info(HasSignature)
397 init_def = info["init_definition"]
420 init_def = info["init_definition"]
398 assert "[0m" not in init_def
421 assert "[0m" not in init_def
399
422
400
423
401 def test_builtin_init():
424 def test_builtin_init():
402 info = inspector.info(list)
425 info = inspector.info(list)
403 init_def = info['init_definition']
426 init_def = info['init_definition']
404 assert init_def is not None
427 assert init_def is not None
405
428
406
429
407 def test_render_signature_short():
430 def test_render_signature_short():
408 def short_fun(a=1): pass
431 def short_fun(a=1): pass
409 sig = oinspect._render_signature(
432 sig = oinspect._render_signature(
410 signature(short_fun),
433 signature(short_fun),
411 short_fun.__name__,
434 short_fun.__name__,
412 )
435 )
413 assert sig == "short_fun(a=1)"
436 assert sig == "short_fun(a=1)"
414
437
415
438
416 def test_render_signature_long():
439 def test_render_signature_long():
417 from typing import Optional
440 from typing import Optional
418
441
419 def long_function(
442 def long_function(
420 a_really_long_parameter: int,
443 a_really_long_parameter: int,
421 and_another_long_one: bool = False,
444 and_another_long_one: bool = False,
422 let_us_make_sure_this_is_looong: Optional[str] = None,
445 let_us_make_sure_this_is_looong: Optional[str] = None,
423 ) -> bool: pass
446 ) -> bool: pass
424
447
425 sig = oinspect._render_signature(
448 sig = oinspect._render_signature(
426 signature(long_function),
449 signature(long_function),
427 long_function.__name__,
450 long_function.__name__,
428 )
451 )
429 assert sig in [
452 assert sig in [
430 # Python >=3.9
453 # Python >=3.9
431 '''\
454 '''\
432 long_function(
455 long_function(
433 a_really_long_parameter: int,
456 a_really_long_parameter: int,
434 and_another_long_one: bool = False,
457 and_another_long_one: bool = False,
435 let_us_make_sure_this_is_looong: Optional[str] = None,
458 let_us_make_sure_this_is_looong: Optional[str] = None,
436 ) -> bool\
459 ) -> bool\
437 ''',
460 ''',
438 # Python >=3.7
461 # Python >=3.7
439 '''\
462 '''\
440 long_function(
463 long_function(
441 a_really_long_parameter: int,
464 a_really_long_parameter: int,
442 and_another_long_one: bool = False,
465 and_another_long_one: bool = False,
443 let_us_make_sure_this_is_looong: Union[str, NoneType] = None,
466 let_us_make_sure_this_is_looong: Union[str, NoneType] = None,
444 ) -> bool\
467 ) -> bool\
445 ''', # Python <=3.6
468 ''', # Python <=3.6
446 '''\
469 '''\
447 long_function(
470 long_function(
448 a_really_long_parameter:int,
471 a_really_long_parameter:int,
449 and_another_long_one:bool=False,
472 and_another_long_one:bool=False,
450 let_us_make_sure_this_is_looong:Union[str, NoneType]=None,
473 let_us_make_sure_this_is_looong:Union[str, NoneType]=None,
451 ) -> bool\
474 ) -> bool\
452 ''',
475 ''',
453 ]
476 ]
General Comments 0
You need to be logged in to leave comments. Login now