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