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