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