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