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