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