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