##// END OF EJS Templates
Add hooking ability for producing help mime bundle....
Matthias Bussonnier -
Show More
@@ -1,1170 +1,1183 b''
1 # -*- coding: utf-8 -*-
2 """Tools for inspecting Python objects.
1 """Tools for inspecting Python objects.
3
2
4 Uses syntax highlighting for presenting the various information elements.
3 Uses syntax highlighting for presenting the various information elements.
5
4
6 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
7 reference the name under which an object is being read.
6 reference the name under which an object is being read.
8 """
7 """
9
8
10 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
12
11
13 __all__ = ['Inspector','InspectColors']
12 __all__ = ['Inspector','InspectColors']
14
13
15 # stdlib modules
14 # stdlib modules
16 from dataclasses import dataclass
15 from dataclasses import dataclass
17 from inspect import signature
16 from inspect import signature
18 from textwrap import dedent
17 from textwrap import dedent
19 import ast
18 import ast
20 import html
19 import html
21 import inspect
20 import inspect
22 import io as stdlib_io
21 import io as stdlib_io
23 import linecache
22 import linecache
24 import os
23 import os
25 import sys
26 import types
24 import types
27 import warnings
25 import warnings
28
26
29 from typing import Any, Optional, Dict, Union, List, Tuple
27 from typing import Any, Optional, Dict, Union, List, Tuple
30
28
31 from typing import TypeAlias
29 from typing import TypeAlias
32
30
31 import traitlets
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, ColorScheme, ColorSchemeTable
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) -> 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 = 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 cast_unicode(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 def __init__(self, color_table=InspectColors,
399 mime_hooks = traitlets.Dict(
400 code_color_table=PyColorize.ANSICodeColors,
400 config=True,
401 scheme=None,
401 help="dictionary of mime to callable to add informations into help mimebundle dict",
402 str_detail_level=0,
402 ).tag(config=True)
403 parent=None, config=None):
403
404 def __init__(
405 self,
406 color_table=InspectColors,
407 code_color_table=PyColorize.ANSICodeColors,
408 scheme=None,
409 str_detail_level=0,
410 parent=None,
411 config=None,
412 ):
404 super(Inspector, self).__init__(parent=parent, config=config)
413 super(Inspector, self).__init__(parent=parent, config=config)
405 self.color_table = color_table
414 self.color_table = color_table
406 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
415 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
407 self.format = self.parser.format
416 self.format = self.parser.format
408 self.str_detail_level = str_detail_level
417 self.str_detail_level = str_detail_level
409 self.set_active_scheme(scheme)
418 self.set_active_scheme(scheme)
410
419
411 def _getdef(self,obj,oname='') -> Union[str,None]:
420 def _getdef(self,obj,oname='') -> Union[str,None]:
412 """Return the call signature for any callable object.
421 """Return the call signature for any callable object.
413
422
414 If any exception is generated, None is returned instead and the
423 If any exception is generated, None is returned instead and the
415 exception is suppressed."""
424 exception is suppressed."""
416 if not callable(obj):
425 if not callable(obj):
417 return None
426 return None
418 try:
427 try:
419 return _render_signature(signature(obj), oname)
428 return _render_signature(signature(obj), oname)
420 except:
429 except:
421 return None
430 return None
422
431
423 def __head(self,h) -> str:
432 def __head(self,h) -> str:
424 """Return a header string with proper colors."""
433 """Return a header string with proper colors."""
425 return '%s%s%s' % (self.color_table.active_colors.header,h,
434 return '%s%s%s' % (self.color_table.active_colors.header,h,
426 self.color_table.active_colors.normal)
435 self.color_table.active_colors.normal)
427
436
428 def set_active_scheme(self, scheme):
437 def set_active_scheme(self, scheme):
429 if scheme is not None:
438 if scheme is not None:
430 self.color_table.set_active_scheme(scheme)
439 self.color_table.set_active_scheme(scheme)
431 self.parser.color_table.set_active_scheme(scheme)
440 self.parser.color_table.set_active_scheme(scheme)
432
441
433 def noinfo(self, msg, oname):
442 def noinfo(self, msg, oname):
434 """Generic message when no information is found."""
443 """Generic message when no information is found."""
435 print('No %s found' % msg, end=' ')
444 print('No %s found' % msg, end=' ')
436 if oname:
445 if oname:
437 print('for %s' % oname)
446 print('for %s' % oname)
438 else:
447 else:
439 print()
448 print()
440
449
441 def pdef(self, obj, oname=''):
450 def pdef(self, obj, oname=''):
442 """Print the call signature for any callable object.
451 """Print the call signature for any callable object.
443
452
444 If the object is a class, print the constructor information."""
453 If the object is a class, print the constructor information."""
445
454
446 if not callable(obj):
455 if not callable(obj):
447 print('Object is not callable.')
456 print('Object is not callable.')
448 return
457 return
449
458
450 header = ''
459 header = ''
451
460
452 if inspect.isclass(obj):
461 if inspect.isclass(obj):
453 header = self.__head('Class constructor information:\n')
462 header = self.__head('Class constructor information:\n')
454
463
455
464
456 output = self._getdef(obj,oname)
465 output = self._getdef(obj,oname)
457 if output is None:
466 if output is None:
458 self.noinfo('definition header',oname)
467 self.noinfo('definition header',oname)
459 else:
468 else:
460 print(header,self.format(output), end=' ')
469 print(header,self.format(output), end=' ')
461
470
462 # 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__.
463 @skip_doctest
472 @skip_doctest
464 def pdoc(self, obj, oname='', formatter=None):
473 def pdoc(self, obj, oname='', formatter=None):
465 """Print the docstring for any object.
474 """Print the docstring for any object.
466
475
467 Optional:
476 Optional:
468 -formatter: a function to run the docstring through for specially
477 -formatter: a function to run the docstring through for specially
469 formatted docstrings.
478 formatted docstrings.
470
479
471 Examples
480 Examples
472 --------
481 --------
473 In [1]: class NoInit:
482 In [1]: class NoInit:
474 ...: pass
483 ...: pass
475
484
476 In [2]: class NoDoc:
485 In [2]: class NoDoc:
477 ...: def __init__(self):
486 ...: def __init__(self):
478 ...: pass
487 ...: pass
479
488
480 In [3]: %pdoc NoDoc
489 In [3]: %pdoc NoDoc
481 No documentation found for NoDoc
490 No documentation found for NoDoc
482
491
483 In [4]: %pdoc NoInit
492 In [4]: %pdoc NoInit
484 No documentation found for NoInit
493 No documentation found for NoInit
485
494
486 In [5]: obj = NoInit()
495 In [5]: obj = NoInit()
487
496
488 In [6]: %pdoc obj
497 In [6]: %pdoc obj
489 No documentation found for obj
498 No documentation found for obj
490
499
491 In [5]: obj2 = NoDoc()
500 In [5]: obj2 = NoDoc()
492
501
493 In [6]: %pdoc obj2
502 In [6]: %pdoc obj2
494 No documentation found for obj2
503 No documentation found for obj2
495 """
504 """
496
505
497 head = self.__head # For convenience
506 head = self.__head # For convenience
498 lines = []
507 lines = []
499 ds = getdoc(obj)
508 ds = getdoc(obj)
500 if formatter:
509 if formatter:
501 ds = formatter(ds).get('plain/text', ds)
510 ds = formatter(ds).get('plain/text', ds)
502 if ds:
511 if ds:
503 lines.append(head("Class docstring:"))
512 lines.append(head("Class docstring:"))
504 lines.append(indent(ds))
513 lines.append(indent(ds))
505 if inspect.isclass(obj) and hasattr(obj, '__init__'):
514 if inspect.isclass(obj) and hasattr(obj, '__init__'):
506 init_ds = getdoc(obj.__init__)
515 init_ds = getdoc(obj.__init__)
507 if init_ds is not None:
516 if init_ds is not None:
508 lines.append(head("Init docstring:"))
517 lines.append(head("Init docstring:"))
509 lines.append(indent(init_ds))
518 lines.append(indent(init_ds))
510 elif hasattr(obj,'__call__'):
519 elif hasattr(obj,'__call__'):
511 call_ds = getdoc(obj.__call__)
520 call_ds = getdoc(obj.__call__)
512 if call_ds:
521 if call_ds:
513 lines.append(head("Call docstring:"))
522 lines.append(head("Call docstring:"))
514 lines.append(indent(call_ds))
523 lines.append(indent(call_ds))
515
524
516 if not lines:
525 if not lines:
517 self.noinfo('documentation',oname)
526 self.noinfo('documentation',oname)
518 else:
527 else:
519 page.page('\n'.join(lines))
528 page.page('\n'.join(lines))
520
529
521 def psource(self, obj, oname=''):
530 def psource(self, obj, oname=''):
522 """Print the source code for an object."""
531 """Print the source code for an object."""
523
532
524 # 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
525 linecache.checkcache()
534 linecache.checkcache()
526 try:
535 try:
527 src = getsource(obj, oname=oname)
536 src = getsource(obj, oname=oname)
528 except Exception:
537 except Exception:
529 src = None
538 src = None
530
539
531 if src is None:
540 if src is None:
532 self.noinfo('source', oname)
541 self.noinfo('source', oname)
533 else:
542 else:
534 page.page(self.format(src))
543 page.page(self.format(src))
535
544
536 def pfile(self, obj, oname=''):
545 def pfile(self, obj, oname=''):
537 """Show the whole file where an object was defined."""
546 """Show the whole file where an object was defined."""
538
547
539 lineno = find_source_lines(obj)
548 lineno = find_source_lines(obj)
540 if lineno is None:
549 if lineno is None:
541 self.noinfo('file', oname)
550 self.noinfo('file', oname)
542 return
551 return
543
552
544 ofile = find_file(obj)
553 ofile = find_file(obj)
545 # 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
546 # 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
547 # filesystem.
556 # filesystem.
548 if ofile.endswith(('.so', '.dll', '.pyd')):
557 if ofile.endswith(('.so', '.dll', '.pyd')):
549 print('File %r is binary, not printing.' % ofile)
558 print('File %r is binary, not printing.' % ofile)
550 elif not os.path.isfile(ofile):
559 elif not os.path.isfile(ofile):
551 print('File %r does not exist, not printing.' % ofile)
560 print('File %r does not exist, not printing.' % ofile)
552 else:
561 else:
553 # Print only text files, not extension binaries. Note that
562 # Print only text files, not extension binaries. Note that
554 # getsourcelines returns lineno with 1-offset and page() uses
563 # getsourcelines returns lineno with 1-offset and page() uses
555 # 0-offset, so we must adjust.
564 # 0-offset, so we must adjust.
556 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)
557
566
558
567
559 def _mime_format(self, text:str, formatter=None) -> dict:
568 def _mime_format(self, text:str, formatter=None) -> dict:
560 """Return a mime bundle representation of the input text.
569 """Return a mime bundle representation of the input text.
561
570
562 - if `formatter` is None, the returned mime bundle has
571 - if `formatter` is None, the returned mime bundle has
563 a ``text/plain`` field, with the input text.
572 a ``text/plain`` field, with the input text.
564 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.
565
574
566 - 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
567 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
568 ``text/html`` representations are the ones described above.
577 ``text/html`` representations are the ones described above.
569
578
570 Note:
579 Note:
571
580
572 Formatters returning strings are supported but this behavior is deprecated.
581 Formatters returning strings are supported but this behavior is deprecated.
573
582
574 """
583 """
575 defaults = {
584 defaults = {
576 "text/plain": text,
585 "text/plain": text,
577 "text/html": f"<pre>{html.escape(text)}</pre>",
586 "text/html": f"<pre>{html.escape(text)}</pre>",
578 }
587 }
579
588
580 if formatter is None:
589 if formatter is None:
581 return defaults
590 return defaults
582 else:
591 else:
583 formatted = formatter(text)
592 formatted = formatter(text)
584
593
585 if not isinstance(formatted, dict):
594 if not isinstance(formatted, dict):
586 # Handle the deprecated behavior of a formatter returning
595 # Handle the deprecated behavior of a formatter returning
587 # a string instead of a mime bundle.
596 # a string instead of a mime bundle.
588 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
597 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
589
598
590 else:
599 else:
591 return dict(defaults, **formatted)
600 return dict(defaults, **formatted)
592
601
593 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
602 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
594 """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"""
595 # Format text/plain mimetype
604 # Format text/plain mimetype
596 assert isinstance(bundle["text/plain"], list)
605 assert isinstance(bundle["text/plain"], list)
597 for item in bundle["text/plain"]:
606 for item in bundle["text/plain"]:
598 assert isinstance(item, tuple)
607 assert isinstance(item, tuple)
599
608
600 new_b: Bundle = {}
609 new_b: Bundle = {}
601 lines = []
610 lines = []
602 _len = max(len(h) for h, _ in bundle["text/plain"])
611 _len = max(len(h) for h, _ in bundle["text/plain"])
603
612
604 for head, body in bundle["text/plain"]:
613 for head, body in bundle["text/plain"]:
605 body = body.strip("\n")
614 body = body.strip("\n")
606 delim = "\n" if "\n" in body else " "
615 delim = "\n" if "\n" in body else " "
607 lines.append(
616 lines.append(
608 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
617 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
609 )
618 )
610
619
611 new_b["text/plain"] = "\n".join(lines)
620 new_b["text/plain"] = "\n".join(lines)
612
621
613 if "text/html" in bundle:
622 if "text/html" in bundle:
614 assert isinstance(bundle["text/html"], list)
623 assert isinstance(bundle["text/html"], list)
615 for item in bundle["text/html"]:
624 for item in bundle["text/html"]:
616 assert isinstance(item, tuple)
625 assert isinstance(item, tuple)
617 # Format the text/html mimetype
626 # Format the text/html mimetype
618 if isinstance(bundle["text/html"], (list, tuple)):
627 if isinstance(bundle["text/html"], (list, tuple)):
619 # bundle['text/html'] is a list of (head, formatted body) pairs
628 # bundle['text/html'] is a list of (head, formatted body) pairs
620 new_b["text/html"] = "\n".join(
629 new_b["text/html"] = "\n".join(
621 (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"])
622 )
631 )
623
632
624 for k in bundle.keys():
633 for k in bundle.keys():
625 if k in ("text/html", "text/plain"):
634 if k in ("text/html", "text/plain"):
626 continue
635 continue
627 else:
636 else:
628 new_b = bundle[k] # type:ignore
637 new_b = bundle[k] # type:ignore
629 return new_b
638 return new_b
630
639
631 def _append_info_field(
640 def _append_info_field(
632 self,
641 self,
633 bundle: UnformattedBundle,
642 bundle: UnformattedBundle,
634 title: str,
643 title: str,
635 key: str,
644 key: str,
636 info,
645 info,
637 omit_sections,
646 omit_sections,
638 formatter,
647 formatter,
639 ):
648 ):
640 """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"""
641 if title in omit_sections or key in omit_sections:
650 if title in omit_sections or key in omit_sections:
642 return
651 return
643 field = info[key]
652 field = info[key]
644 if field is not None:
653 if field is not None:
645 formatted_field = self._mime_format(field, formatter)
654 formatted_field = self._mime_format(field, formatter)
646 bundle["text/plain"].append((title, formatted_field["text/plain"]))
655 bundle["text/plain"].append((title, formatted_field["text/plain"]))
647 bundle["text/html"].append((title, formatted_field["text/html"]))
656 bundle["text/html"].append((title, formatted_field["text/html"]))
648
657
649 def _make_info_unformatted(
658 def _make_info_unformatted(
650 self, obj, info, formatter, detail_level, omit_sections
659 self, obj, info, formatter, detail_level, omit_sections
651 ) -> UnformattedBundle:
660 ) -> UnformattedBundle:
652 """Assemble the mimebundle as unformatted lists of information"""
661 """Assemble the mimebundle as unformatted lists of information"""
653 bundle: UnformattedBundle = {
662 bundle: UnformattedBundle = {
654 "text/plain": [],
663 "text/plain": [],
655 "text/html": [],
664 "text/html": [],
656 }
665 }
657
666
658 # A convenience function to simplify calls below
667 # A convenience function to simplify calls below
659 def append_field(
668 def append_field(
660 bundle: UnformattedBundle, title: str, key: str, formatter=None
669 bundle: UnformattedBundle, title: str, key: str, formatter=None
661 ):
670 ):
662 self._append_info_field(
671 self._append_info_field(
663 bundle,
672 bundle,
664 title=title,
673 title=title,
665 key=key,
674 key=key,
666 info=info,
675 info=info,
667 omit_sections=omit_sections,
676 omit_sections=omit_sections,
668 formatter=formatter,
677 formatter=formatter,
669 )
678 )
670
679
671 def code_formatter(text) -> Bundle:
680 def code_formatter(text) -> Bundle:
672 return {
681 return {
673 'text/plain': self.format(text),
682 'text/plain': self.format(text),
674 'text/html': pylight(text)
683 'text/html': pylight(text)
675 }
684 }
676
685
677 if info["isalias"]:
686 if info["isalias"]:
678 append_field(bundle, "Repr", "string_form")
687 append_field(bundle, "Repr", "string_form")
679
688
680 elif info['ismagic']:
689 elif info['ismagic']:
681 if detail_level > 0:
690 if detail_level > 0:
682 append_field(bundle, "Source", "source", code_formatter)
691 append_field(bundle, "Source", "source", code_formatter)
683 else:
692 else:
684 append_field(bundle, "Docstring", "docstring", formatter)
693 append_field(bundle, "Docstring", "docstring", formatter)
685 append_field(bundle, "File", "file")
694 append_field(bundle, "File", "file")
686
695
687 elif info['isclass'] or is_simple_callable(obj):
696 elif info['isclass'] or is_simple_callable(obj):
688 # Functions, methods, classes
697 # Functions, methods, classes
689 append_field(bundle, "Signature", "definition", code_formatter)
698 append_field(bundle, "Signature", "definition", code_formatter)
690 append_field(bundle, "Init signature", "init_definition", code_formatter)
699 append_field(bundle, "Init signature", "init_definition", code_formatter)
691 append_field(bundle, "Docstring", "docstring", formatter)
700 append_field(bundle, "Docstring", "docstring", formatter)
692 if detail_level > 0 and info["source"]:
701 if detail_level > 0 and info["source"]:
693 append_field(bundle, "Source", "source", code_formatter)
702 append_field(bundle, "Source", "source", code_formatter)
694 else:
703 else:
695 append_field(bundle, "Init docstring", "init_docstring", formatter)
704 append_field(bundle, "Init docstring", "init_docstring", formatter)
696
705
697 append_field(bundle, "File", "file")
706 append_field(bundle, "File", "file")
698 append_field(bundle, "Type", "type_name")
707 append_field(bundle, "Type", "type_name")
699 append_field(bundle, "Subclasses", "subclasses")
708 append_field(bundle, "Subclasses", "subclasses")
700
709
701 else:
710 else:
702 # General Python objects
711 # General Python objects
703 append_field(bundle, "Signature", "definition", code_formatter)
712 append_field(bundle, "Signature", "definition", code_formatter)
704 append_field(bundle, "Call signature", "call_def", code_formatter)
713 append_field(bundle, "Call signature", "call_def", code_formatter)
705 append_field(bundle, "Type", "type_name")
714 append_field(bundle, "Type", "type_name")
706 append_field(bundle, "String form", "string_form")
715 append_field(bundle, "String form", "string_form")
707
716
708 # Namespace
717 # Namespace
709 if info["namespace"] != "Interactive":
718 if info["namespace"] != "Interactive":
710 append_field(bundle, "Namespace", "namespace")
719 append_field(bundle, "Namespace", "namespace")
711
720
712 append_field(bundle, "Length", "length")
721 append_field(bundle, "Length", "length")
713 append_field(bundle, "File", "file")
722 append_field(bundle, "File", "file")
714
723
715 # Source or docstring, depending on detail level and whether
724 # Source or docstring, depending on detail level and whether
716 # source found.
725 # source found.
717 if detail_level > 0 and info["source"]:
726 if detail_level > 0 and info["source"]:
718 append_field(bundle, "Source", "source", code_formatter)
727 append_field(bundle, "Source", "source", code_formatter)
719 else:
728 else:
720 append_field(bundle, "Docstring", "docstring", formatter)
729 append_field(bundle, "Docstring", "docstring", formatter)
721
730
722 append_field(bundle, "Class docstring", "class_docstring", formatter)
731 append_field(bundle, "Class docstring", "class_docstring", formatter)
723 append_field(bundle, "Init docstring", "init_docstring", formatter)
732 append_field(bundle, "Init docstring", "init_docstring", formatter)
724 append_field(bundle, "Call docstring", "call_docstring", formatter)
733 append_field(bundle, "Call docstring", "call_docstring", formatter)
725 return bundle
734 return bundle
726
735
727
736
728 def _get_info(
737 def _get_info(
729 self,
738 self,
730 obj: Any,
739 obj: Any,
731 oname: str = "",
740 oname: str = "",
732 formatter=None,
741 formatter=None,
733 info: Optional[OInfo] = None,
742 info: Optional[OInfo] = None,
734 detail_level=0,
743 detail_level=0,
735 omit_sections=(),
744 omit_sections=(),
736 ) -> Bundle:
745 ) -> Bundle:
737 """Retrieve an info dict and format it.
746 """Retrieve an info dict and format it.
738
747
739 Parameters
748 Parameters
740 ----------
749 ----------
741 obj : any
750 obj : any
742 Object to inspect and return info from
751 Object to inspect and return info from
743 oname : str (default: ''):
752 oname : str (default: ''):
744 Name of the variable pointing to `obj`.
753 Name of the variable pointing to `obj`.
745 formatter : callable
754 formatter : callable
746 info
755 info
747 already computed information
756 already computed information
748 detail_level : integer
757 detail_level : integer
749 Granularity of detail level, if set to 1, give more information.
758 Granularity of detail level, if set to 1, give more information.
750 omit_sections : container[str]
759 omit_sections : container[str]
751 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`)
752 """
761 """
753
762
754 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)
755 bundle = self._make_info_unformatted(
764 bundle = self._make_info_unformatted(
756 obj,
765 obj,
757 info_dict,
766 info_dict,
758 formatter,
767 formatter,
759 detail_level=detail_level,
768 detail_level=detail_level,
760 omit_sections=omit_sections,
769 omit_sections=omit_sections,
761 )
770 )
771 for key, hook in self.mime_hooks.items():
772 res = hook(obj, info)
773 if res is not None:
774 bundle[key] = res
762 return self.format_mime(bundle)
775 return self.format_mime(bundle)
763
776
764 def pinfo(
777 def pinfo(
765 self,
778 self,
766 obj,
779 obj,
767 oname="",
780 oname="",
768 formatter=None,
781 formatter=None,
769 info: Optional[OInfo] = None,
782 info: Optional[OInfo] = None,
770 detail_level=0,
783 detail_level=0,
771 enable_html_pager=True,
784 enable_html_pager=True,
772 omit_sections=(),
785 omit_sections=(),
773 ):
786 ):
774 """Show detailed information about an object.
787 """Show detailed information about an object.
775
788
776 Optional arguments:
789 Optional arguments:
777
790
778 - oname: name of the variable pointing to the object.
791 - oname: name of the variable pointing to the object.
779
792
780 - formatter: callable (optional)
793 - formatter: callable (optional)
781 A special formatter for docstrings.
794 A special formatter for docstrings.
782
795
783 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
784 and returns either a formatted string or a mime type bundle
797 and returns either a formatted string or a mime type bundle
785 in the form of a dictionary.
798 in the form of a dictionary.
786
799
787 Although the support of custom formatter returning a string
800 Although the support of custom formatter returning a string
788 instead of a mime type bundle is deprecated.
801 instead of a mime type bundle is deprecated.
789
802
790 - info: a structure with some information fields which may have been
803 - info: a structure with some information fields which may have been
791 precomputed already.
804 precomputed already.
792
805
793 - detail_level: if set to 1, more information is given.
806 - detail_level: if set to 1, more information is given.
794
807
795 - omit_sections: set of section keys and titles to omit
808 - omit_sections: set of section keys and titles to omit
796 """
809 """
797 assert info is not None
810 assert info is not None
798 info_b: Bundle = self._get_info(
811 info_b: Bundle = self._get_info(
799 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
812 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
800 )
813 )
801 if not enable_html_pager:
814 if not enable_html_pager:
802 del info_b["text/html"]
815 del info_b["text/html"]
803 page.page(info_b)
816 page.page(info_b)
804
817
805 def _info(self, obj, oname="", info=None, detail_level=0):
818 def _info(self, obj, oname="", info=None, detail_level=0):
806 """
819 """
807 Inspector.info() was likely improperly marked as deprecated
820 Inspector.info() was likely improperly marked as deprecated
808 while only a parameter was deprecated. We "un-deprecate" it.
821 while only a parameter was deprecated. We "un-deprecate" it.
809 """
822 """
810
823
811 warnings.warn(
824 warnings.warn(
812 "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 "
813 "and the `formatter=` keyword removed. `Inspector._info` is now "
826 "and the `formatter=` keyword removed. `Inspector._info` is now "
814 "an alias, and you can just call `.info()` directly.",
827 "an alias, and you can just call `.info()` directly.",
815 DeprecationWarning,
828 DeprecationWarning,
816 stacklevel=2,
829 stacklevel=2,
817 )
830 )
818 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)
819
832
820 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]:
821 """Compute a dict with detailed information about an object.
834 """Compute a dict with detailed information about an object.
822
835
823 Parameters
836 Parameters
824 ----------
837 ----------
825 obj : any
838 obj : any
826 An object to find information about
839 An object to find information about
827 oname : str (default: '')
840 oname : str (default: '')
828 Name of the variable pointing to `obj`.
841 Name of the variable pointing to `obj`.
829 info : (default: None)
842 info : (default: None)
830 A struct (dict like with attr access) with some information fields
843 A struct (dict like with attr access) with some information fields
831 which may have been precomputed already.
844 which may have been precomputed already.
832 detail_level : int (default:0)
845 detail_level : int (default:0)
833 If set to 1, more information is given.
846 If set to 1, more information is given.
834
847
835 Returns
848 Returns
836 -------
849 -------
837 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
838 strings, values are string or None.
851 strings, values are string or None.
839 """
852 """
840
853
841 if info is None:
854 if info is None:
842 ismagic = False
855 ismagic = False
843 isalias = False
856 isalias = False
844 ospace = ''
857 ospace = ''
845 else:
858 else:
846 ismagic = info.ismagic
859 ismagic = info.ismagic
847 isalias = info.isalias
860 isalias = info.isalias
848 ospace = info.namespace
861 ospace = info.namespace
849
862
850 # Get docstring, special-casing aliases:
863 # Get docstring, special-casing aliases:
851 att_name = oname.split(".")[-1]
864 att_name = oname.split(".")[-1]
852 parents_docs = None
865 parents_docs = None
853 prelude = ""
866 prelude = ""
854 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):
855 parents_docs_dict = getattr(info.parent, HOOK_NAME)
868 parents_docs_dict = getattr(info.parent, HOOK_NAME)
856 parents_docs = parents_docs_dict.get(att_name, None)
869 parents_docs = parents_docs_dict.get(att_name, None)
857 out = dict(
870 out = dict(
858 name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None
871 name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None
859 )
872 )
860
873
861 if parents_docs:
874 if parents_docs:
862 ds = parents_docs
875 ds = parents_docs
863 elif isalias:
876 elif isalias:
864 if not callable(obj):
877 if not callable(obj):
865 try:
878 try:
866 ds = "Alias to the system command:\n %s" % obj[1]
879 ds = "Alias to the system command:\n %s" % obj[1]
867 except:
880 except:
868 ds = "Alias: " + str(obj)
881 ds = "Alias: " + str(obj)
869 else:
882 else:
870 ds = "Alias to " + str(obj)
883 ds = "Alias to " + str(obj)
871 if obj.__doc__:
884 if obj.__doc__:
872 ds += "\nDocstring:\n" + obj.__doc__
885 ds += "\nDocstring:\n" + obj.__doc__
873 else:
886 else:
874 ds_or_None = getdoc(obj)
887 ds_or_None = getdoc(obj)
875 if ds_or_None is None:
888 if ds_or_None is None:
876 ds = '<no docstring>'
889 ds = '<no docstring>'
877 else:
890 else:
878 ds = ds_or_None
891 ds = ds_or_None
879
892
880 ds = prelude + ds
893 ds = prelude + ds
881
894
882 # 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
883
896
884 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)
885 shalf = int((string_max - 5) / 2)
898 shalf = int((string_max - 5) / 2)
886
899
887 if ismagic:
900 if ismagic:
888 out['type_name'] = 'Magic function'
901 out['type_name'] = 'Magic function'
889 elif isalias:
902 elif isalias:
890 out['type_name'] = 'System alias'
903 out['type_name'] = 'System alias'
891 else:
904 else:
892 out['type_name'] = type(obj).__name__
905 out['type_name'] = type(obj).__name__
893
906
894 try:
907 try:
895 bclass = obj.__class__
908 bclass = obj.__class__
896 out['base_class'] = str(bclass)
909 out['base_class'] = str(bclass)
897 except:
910 except:
898 pass
911 pass
899
912
900 # String form, but snip if too long in ? form (full in ??)
913 # String form, but snip if too long in ? form (full in ??)
901 if detail_level >= self.str_detail_level:
914 if detail_level >= self.str_detail_level:
902 try:
915 try:
903 ostr = str(obj)
916 ostr = str(obj)
904 str_head = 'string_form'
917 str_head = 'string_form'
905 if not detail_level and len(ostr)>string_max:
918 if not detail_level and len(ostr)>string_max:
906 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
919 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
907 ostr = ("\n" + " " * len(str_head.expandtabs())).\
920 ostr = ("\n" + " " * len(str_head.expandtabs())).\
908 join(q.strip() for q in ostr.split("\n"))
921 join(q.strip() for q in ostr.split("\n"))
909 out[str_head] = ostr
922 out[str_head] = ostr
910 except:
923 except:
911 pass
924 pass
912
925
913 if ospace:
926 if ospace:
914 out['namespace'] = ospace
927 out['namespace'] = ospace
915
928
916 # Length (for strings and lists)
929 # Length (for strings and lists)
917 try:
930 try:
918 out['length'] = str(len(obj))
931 out['length'] = str(len(obj))
919 except Exception:
932 except Exception:
920 pass
933 pass
921
934
922 # Filename where object was defined
935 # Filename where object was defined
923 binary_file = False
936 binary_file = False
924 fname = find_file(obj)
937 fname = find_file(obj)
925 if fname is None:
938 if fname is None:
926 # 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
927 # if the file was binary
940 # if the file was binary
928 binary_file = True
941 binary_file = True
929 else:
942 else:
930 if fname.endswith(('.so', '.dll', '.pyd')):
943 if fname.endswith(('.so', '.dll', '.pyd')):
931 binary_file = True
944 binary_file = True
932 elif fname.endswith('<string>'):
945 elif fname.endswith('<string>'):
933 fname = 'Dynamically generated function. No source code available.'
946 fname = 'Dynamically generated function. No source code available.'
934 out['file'] = compress_user(fname)
947 out['file'] = compress_user(fname)
935
948
936 # Original source code for a callable, class or property.
949 # Original source code for a callable, class or property.
937 if detail_level:
950 if detail_level:
938 # Flush the source cache because inspect can return out-of-date
951 # Flush the source cache because inspect can return out-of-date
939 # source
952 # source
940 linecache.checkcache()
953 linecache.checkcache()
941 try:
954 try:
942 if isinstance(obj, property) or not binary_file:
955 if isinstance(obj, property) or not binary_file:
943 src = getsource(obj, oname)
956 src = getsource(obj, oname)
944 if src is not None:
957 if src is not None:
945 src = src.rstrip()
958 src = src.rstrip()
946 out['source'] = src
959 out['source'] = src
947
960
948 except Exception:
961 except Exception:
949 pass
962 pass
950
963
951 # 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).
952 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):
953 out['docstring'] = ds
966 out['docstring'] = ds
954
967
955 # Constructor docstring for classes
968 # Constructor docstring for classes
956 if inspect.isclass(obj):
969 if inspect.isclass(obj):
957 out['isclass'] = True
970 out['isclass'] = True
958
971
959 # get the init signature:
972 # get the init signature:
960 try:
973 try:
961 init_def = self._getdef(obj, oname)
974 init_def = self._getdef(obj, oname)
962 except AttributeError:
975 except AttributeError:
963 init_def = None
976 init_def = None
964
977
965 # get the __init__ docstring
978 # get the __init__ docstring
966 try:
979 try:
967 obj_init = obj.__init__
980 obj_init = obj.__init__
968 except AttributeError:
981 except AttributeError:
969 init_ds = None
982 init_ds = None
970 else:
983 else:
971 if init_def is None:
984 if init_def is None:
972 # Get signature from init if top-level sig failed.
985 # Get signature from init if top-level sig failed.
973 # Can happen for built-in types (list, etc.).
986 # Can happen for built-in types (list, etc.).
974 try:
987 try:
975 init_def = self._getdef(obj_init, oname)
988 init_def = self._getdef(obj_init, oname)
976 except AttributeError:
989 except AttributeError:
977 pass
990 pass
978 init_ds = getdoc(obj_init)
991 init_ds = getdoc(obj_init)
979 # Skip Python's auto-generated docstrings
992 # Skip Python's auto-generated docstrings
980 if init_ds == _object_init_docstring:
993 if init_ds == _object_init_docstring:
981 init_ds = None
994 init_ds = None
982
995
983 if init_def:
996 if init_def:
984 out['init_definition'] = init_def
997 out['init_definition'] = init_def
985
998
986 if init_ds:
999 if init_ds:
987 out['init_docstring'] = init_ds
1000 out['init_docstring'] = init_ds
988
1001
989 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1002 names = [sub.__name__ for sub in type.__subclasses__(obj)]
990 if len(names) < 10:
1003 if len(names) < 10:
991 all_names = ', '.join(names)
1004 all_names = ', '.join(names)
992 else:
1005 else:
993 all_names = ', '.join(names[:10]+['...'])
1006 all_names = ', '.join(names[:10]+['...'])
994 out['subclasses'] = all_names
1007 out['subclasses'] = all_names
995 # and class docstring for instances:
1008 # and class docstring for instances:
996 else:
1009 else:
997 # reconstruct the function definition and print it:
1010 # reconstruct the function definition and print it:
998 defln = self._getdef(obj, oname)
1011 defln = self._getdef(obj, oname)
999 if defln:
1012 if defln:
1000 out['definition'] = defln
1013 out['definition'] = defln
1001
1014
1002 # First, check whether the instance docstring is identical to the
1015 # First, check whether the instance docstring is identical to the
1003 # 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
1004 # 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
1005 # objects which use instance-customized docstrings.
1018 # objects which use instance-customized docstrings.
1006 if ds:
1019 if ds:
1007 try:
1020 try:
1008 cls = getattr(obj,'__class__')
1021 cls = getattr(obj,'__class__')
1009 except:
1022 except:
1010 class_ds = None
1023 class_ds = None
1011 else:
1024 else:
1012 class_ds = getdoc(cls)
1025 class_ds = getdoc(cls)
1013 # Skip Python's auto-generated docstrings
1026 # Skip Python's auto-generated docstrings
1014 if class_ds in _builtin_type_docstrings:
1027 if class_ds in _builtin_type_docstrings:
1015 class_ds = None
1028 class_ds = None
1016 if class_ds and ds != class_ds:
1029 if class_ds and ds != class_ds:
1017 out['class_docstring'] = class_ds
1030 out['class_docstring'] = class_ds
1018
1031
1019 # Next, try to show constructor docstrings
1032 # Next, try to show constructor docstrings
1020 try:
1033 try:
1021 init_ds = getdoc(obj.__init__)
1034 init_ds = getdoc(obj.__init__)
1022 # Skip Python's auto-generated docstrings
1035 # Skip Python's auto-generated docstrings
1023 if init_ds == _object_init_docstring:
1036 if init_ds == _object_init_docstring:
1024 init_ds = None
1037 init_ds = None
1025 except AttributeError:
1038 except AttributeError:
1026 init_ds = None
1039 init_ds = None
1027 if init_ds:
1040 if init_ds:
1028 out['init_docstring'] = init_ds
1041 out['init_docstring'] = init_ds
1029
1042
1030 # Call form docstring for callable instances
1043 # Call form docstring for callable instances
1031 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1044 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1032 call_def = self._getdef(obj.__call__, oname)
1045 call_def = self._getdef(obj.__call__, oname)
1033 if call_def and (call_def != out.get('definition')):
1046 if call_def and (call_def != out.get('definition')):
1034 # 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,
1035 # but don't include the same signature twice
1048 # but don't include the same signature twice
1036 out['call_def'] = call_def
1049 out['call_def'] = call_def
1037 call_ds = getdoc(obj.__call__)
1050 call_ds = getdoc(obj.__call__)
1038 # Skip Python's auto-generated docstrings
1051 # Skip Python's auto-generated docstrings
1039 if call_ds == _func_call_docstring:
1052 if call_ds == _func_call_docstring:
1040 call_ds = None
1053 call_ds = None
1041 if call_ds:
1054 if call_ds:
1042 out['call_docstring'] = call_ds
1055 out['call_docstring'] = call_ds
1043
1056
1044 return object_info(**out)
1057 return object_info(**out)
1045
1058
1046 @staticmethod
1059 @staticmethod
1047 def _source_contains_docstring(src, doc):
1060 def _source_contains_docstring(src, doc):
1048 """
1061 """
1049 Check whether the source *src* contains the docstring *doc*.
1062 Check whether the source *src* contains the docstring *doc*.
1050
1063
1051 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
1052 source already contains it, avoiding repetition of information.
1065 source already contains it, avoiding repetition of information.
1053 """
1066 """
1054 try:
1067 try:
1055 (def_node,) = ast.parse(dedent(src)).body
1068 (def_node,) = ast.parse(dedent(src)).body
1056 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1069 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1057 except Exception:
1070 except Exception:
1058 # The source can become invalid or even non-existent (because it
1071 # The source can become invalid or even non-existent (because it
1059 # 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
1060 # arbitrary ways.
1073 # arbitrary ways.
1061 return False
1074 return False
1062
1075
1063 def psearch(self,pattern,ns_table,ns_search=[],
1076 def psearch(self,pattern,ns_table,ns_search=[],
1064 ignore_case=False,show_all=False, *, list_types=False):
1077 ignore_case=False,show_all=False, *, list_types=False):
1065 """Search namespaces with wildcards for objects.
1078 """Search namespaces with wildcards for objects.
1066
1079
1067 Arguments:
1080 Arguments:
1068
1081
1069 - pattern: string containing shell-like wildcards to use in namespace
1082 - pattern: string containing shell-like wildcards to use in namespace
1070 searches and optionally a type specification to narrow the search to
1083 searches and optionally a type specification to narrow the search to
1071 objects of that type.
1084 objects of that type.
1072
1085
1073 - ns_table: dict of name->namespaces for search.
1086 - ns_table: dict of name->namespaces for search.
1074
1087
1075 Optional arguments:
1088 Optional arguments:
1076
1089
1077 - ns_search: list of namespace names to include in search.
1090 - ns_search: list of namespace names to include in search.
1078
1091
1079 - ignore_case(False): make the search case-insensitive.
1092 - ignore_case(False): make the search case-insensitive.
1080
1093
1081 - show_all(False): show all names, including those starting with
1094 - show_all(False): show all names, including those starting with
1082 underscores.
1095 underscores.
1083
1096
1084 - list_types(False): list all available object types for object matching.
1097 - list_types(False): list all available object types for object matching.
1085 """
1098 """
1086 #print 'ps pattern:<%r>' % pattern # dbg
1099 #print 'ps pattern:<%r>' % pattern # dbg
1087
1100
1088 # defaults
1101 # defaults
1089 type_pattern = 'all'
1102 type_pattern = 'all'
1090 filter = ''
1103 filter = ''
1091
1104
1092 # list all object types
1105 # list all object types
1093 if list_types:
1106 if list_types:
1094 page.page('\n'.join(sorted(typestr2type)))
1107 page.page('\n'.join(sorted(typestr2type)))
1095 return
1108 return
1096
1109
1097 cmds = pattern.split()
1110 cmds = pattern.split()
1098 len_cmds = len(cmds)
1111 len_cmds = len(cmds)
1099 if len_cmds == 1:
1112 if len_cmds == 1:
1100 # Only filter pattern given
1113 # Only filter pattern given
1101 filter = cmds[0]
1114 filter = cmds[0]
1102 elif len_cmds == 2:
1115 elif len_cmds == 2:
1103 # Both filter and type specified
1116 # Both filter and type specified
1104 filter,type_pattern = cmds
1117 filter,type_pattern = cmds
1105 else:
1118 else:
1106 raise ValueError('invalid argument string for psearch: <%s>' %
1119 raise ValueError('invalid argument string for psearch: <%s>' %
1107 pattern)
1120 pattern)
1108
1121
1109 # filter search namespaces
1122 # filter search namespaces
1110 for name in ns_search:
1123 for name in ns_search:
1111 if name not in ns_table:
1124 if name not in ns_table:
1112 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1125 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1113 (name,ns_table.keys()))
1126 (name,ns_table.keys()))
1114
1127
1115 #print 'type_pattern:',type_pattern # dbg
1128 #print 'type_pattern:',type_pattern # dbg
1116 search_result, namespaces_seen = set(), set()
1129 search_result, namespaces_seen = set(), set()
1117 for ns_name in ns_search:
1130 for ns_name in ns_search:
1118 ns = ns_table[ns_name]
1131 ns = ns_table[ns_name]
1119 # 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.
1120 if id(ns) in namespaces_seen:
1133 if id(ns) in namespaces_seen:
1121 continue
1134 continue
1122 namespaces_seen.add(id(ns))
1135 namespaces_seen.add(id(ns))
1123 tmp_res = list_namespace(ns, type_pattern, filter,
1136 tmp_res = list_namespace(ns, type_pattern, filter,
1124 ignore_case=ignore_case, show_all=show_all)
1137 ignore_case=ignore_case, show_all=show_all)
1125 search_result.update(tmp_res)
1138 search_result.update(tmp_res)
1126
1139
1127 page.page('\n'.join(sorted(search_result)))
1140 page.page('\n'.join(sorted(search_result)))
1128
1141
1129
1142
1130 def _render_signature(obj_signature, obj_name) -> str:
1143 def _render_signature(obj_signature, obj_name) -> str:
1131 """
1144 """
1132 This was mostly taken from inspect.Signature.__str__.
1145 This was mostly taken from inspect.Signature.__str__.
1133 Look there for the comments.
1146 Look there for the comments.
1134 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.
1135 """
1148 """
1136 result = []
1149 result = []
1137 pos_only = False
1150 pos_only = False
1138 kw_only = True
1151 kw_only = True
1139 for param in obj_signature.parameters.values():
1152 for param in obj_signature.parameters.values():
1140 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1153 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1141 pos_only = True
1154 pos_only = True
1142 elif pos_only:
1155 elif pos_only:
1143 result.append('/')
1156 result.append('/')
1144 pos_only = False
1157 pos_only = False
1145
1158
1146 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1159 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1147 kw_only = False
1160 kw_only = False
1148 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1161 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1149 result.append('*')
1162 result.append('*')
1150 kw_only = False
1163 kw_only = False
1151
1164
1152 result.append(str(param))
1165 result.append(str(param))
1153
1166
1154 if pos_only:
1167 if pos_only:
1155 result.append('/')
1168 result.append('/')
1156
1169
1157 # add up name, parameters, braces (2), and commas
1170 # add up name, parameters, braces (2), and commas
1158 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:
1159 # This doesn’t fit behind “Signature: ” in an inspect window.
1172 # This doesn’t fit behind “Signature: ” in an inspect window.
1160 rendered = '{}(\n{})'.format(obj_name, ''.join(
1173 rendered = '{}(\n{})'.format(obj_name, ''.join(
1161 ' {},\n'.format(r) for r in result)
1174 ' {},\n'.format(r) for r in result)
1162 )
1175 )
1163 else:
1176 else:
1164 rendered = '{}({})'.format(obj_name, ', '.join(result))
1177 rendered = '{}({})'.format(obj_name, ', '.join(result))
1165
1178
1166 if obj_signature.return_annotation is not inspect._empty:
1179 if obj_signature.return_annotation is not inspect._empty:
1167 anno = inspect.formatannotation(obj_signature.return_annotation)
1180 anno = inspect.formatannotation(obj_signature.return_annotation)
1168 rendered += ' -> {}'.format(anno)
1181 rendered += ' -> {}'.format(anno)
1169
1182
1170 return rendered
1183 return rendered
General Comments 0
You need to be logged in to leave comments. Login now