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