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