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