|
@@
-1,950
+1,1011
|
|
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
|
from __future__ import print_function
|
|
13
|
from __future__ import print_function
|
|
14
|
|
|
14
|
|
|
15
|
__all__ = ['Inspector','InspectColors']
|
|
15
|
__all__ = ['Inspector','InspectColors']
|
|
16
|
|
|
16
|
|
|
17
|
# stdlib modules
|
|
17
|
# stdlib modules
|
|
18
|
import inspect
|
|
18
|
import inspect
|
|
19
|
import linecache
|
|
19
|
import linecache
|
|
20
|
import warnings
|
|
20
|
import warnings
|
|
21
|
import os
|
|
21
|
import os
|
|
22
|
from textwrap import dedent
|
|
22
|
from textwrap import dedent
|
|
23
|
import types
|
|
23
|
import types
|
|
24
|
import io as stdlib_io
|
|
24
|
import io as stdlib_io
|
|
25
|
|
|
25
|
|
|
26
|
try:
|
|
26
|
try:
|
|
27
|
from itertools import izip_longest
|
|
27
|
from itertools import izip_longest
|
|
28
|
except ImportError:
|
|
28
|
except ImportError:
|
|
29
|
from itertools import zip_longest as izip_longest
|
|
29
|
from itertools import zip_longest as izip_longest
|
|
30
|
|
|
30
|
|
|
31
|
# IPython's own
|
|
31
|
# IPython's own
|
|
32
|
from IPython.core import page
|
|
32
|
from IPython.core import page
|
|
33
|
from IPython.lib.pretty import pretty
|
|
33
|
from IPython.lib.pretty import pretty
|
|
|
|
|
34
|
from IPython.testing.skipdoctest import skip_doctest
|
|
34
|
from IPython.utils import PyColorize
|
|
35
|
from IPython.utils import PyColorize
|
|
35
|
from IPython.utils import openpy
|
|
36
|
from IPython.utils import openpy
|
|
36
|
from IPython.utils import py3compat
|
|
37
|
from IPython.utils import py3compat
|
|
37
|
from IPython.utils.dir2 import safe_hasattr
|
|
38
|
from IPython.utils.dir2 import safe_hasattr
|
|
38
|
from IPython.utils.path import compress_user
|
|
39
|
from IPython.utils.path import compress_user
|
|
39
|
from IPython.utils.text import indent
|
|
40
|
from IPython.utils.text import indent
|
|
40
|
from IPython.utils.wildcard import list_namespace
|
|
41
|
from IPython.utils.wildcard import list_namespace
|
|
41
|
from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
|
|
42
|
from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
|
|
42
|
from IPython.utils.py3compat import cast_unicode, string_types, PY3
|
|
43
|
from IPython.utils.py3compat import cast_unicode, string_types, PY3
|
|
43
|
from IPython.utils.signatures import signature
|
|
44
|
from IPython.utils.signatures import signature
|
|
44
|
from IPython.utils.colorable import Colorable
|
|
45
|
from IPython.utils.colorable import Colorable
|
|
45
|
|
|
46
|
|
|
46
|
from pygments import highlight
|
|
47
|
from pygments import highlight
|
|
47
|
from pygments.lexers import PythonLexer
|
|
48
|
from pygments.lexers import PythonLexer
|
|
48
|
from pygments.formatters import HtmlFormatter
|
|
49
|
from pygments.formatters import HtmlFormatter
|
|
49
|
|
|
50
|
|
|
50
|
def pylight(code):
|
|
51
|
def pylight(code):
|
|
51
|
return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
|
|
52
|
return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
|
|
52
|
|
|
53
|
|
|
53
|
# builtin docstrings to ignore
|
|
54
|
# builtin docstrings to ignore
|
|
54
|
_func_call_docstring = types.FunctionType.__call__.__doc__
|
|
55
|
_func_call_docstring = types.FunctionType.__call__.__doc__
|
|
55
|
_object_init_docstring = object.__init__.__doc__
|
|
56
|
_object_init_docstring = object.__init__.__doc__
|
|
56
|
_builtin_type_docstrings = {
|
|
57
|
_builtin_type_docstrings = {
|
|
57
|
inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
|
|
58
|
inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
|
|
58
|
types.FunctionType, property)
|
|
59
|
types.FunctionType, property)
|
|
59
|
}
|
|
60
|
}
|
|
60
|
|
|
61
|
|
|
61
|
_builtin_func_type = type(all)
|
|
62
|
_builtin_func_type = type(all)
|
|
62
|
_builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
|
|
63
|
_builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
|
|
63
|
#****************************************************************************
|
|
64
|
#****************************************************************************
|
|
64
|
# Builtin color schemes
|
|
65
|
# Builtin color schemes
|
|
65
|
|
|
66
|
|
|
66
|
Colors = TermColors # just a shorthand
|
|
67
|
Colors = TermColors # just a shorthand
|
|
67
|
|
|
68
|
|
|
68
|
InspectColors = PyColorize.ANSICodeColors
|
|
69
|
InspectColors = PyColorize.ANSICodeColors
|
|
69
|
|
|
70
|
|
|
70
|
#****************************************************************************
|
|
71
|
#****************************************************************************
|
|
71
|
# Auxiliary functions and objects
|
|
72
|
# Auxiliary functions and objects
|
|
72
|
|
|
73
|
|
|
73
|
# See the messaging spec for the definition of all these fields. This list
|
|
74
|
# See the messaging spec for the definition of all these fields. This list
|
|
74
|
# effectively defines the order of display
|
|
75
|
# effectively defines the order of display
|
|
75
|
info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
|
|
76
|
info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
|
|
76
|
'length', 'file', 'definition', 'docstring', 'source',
|
|
77
|
'length', 'file', 'definition', 'docstring', 'source',
|
|
77
|
'init_definition', 'class_docstring', 'init_docstring',
|
|
78
|
'init_definition', 'class_docstring', 'init_docstring',
|
|
78
|
'call_def', 'call_docstring',
|
|
79
|
'call_def', 'call_docstring',
|
|
79
|
# These won't be printed but will be used to determine how to
|
|
80
|
# These won't be printed but will be used to determine how to
|
|
80
|
# format the object
|
|
81
|
# format the object
|
|
81
|
'ismagic', 'isalias', 'isclass', 'argspec', 'found', 'name'
|
|
82
|
'ismagic', 'isalias', 'isclass', 'argspec', 'found', 'name'
|
|
82
|
]
|
|
83
|
]
|
|
83
|
|
|
84
|
|
|
84
|
|
|
85
|
|
|
85
|
def object_info(**kw):
|
|
86
|
def object_info(**kw):
|
|
86
|
"""Make an object info dict with all fields present."""
|
|
87
|
"""Make an object info dict with all fields present."""
|
|
87
|
infodict = dict(izip_longest(info_fields, [None]))
|
|
88
|
infodict = dict(izip_longest(info_fields, [None]))
|
|
88
|
infodict.update(kw)
|
|
89
|
infodict.update(kw)
|
|
89
|
return infodict
|
|
90
|
return infodict
|
|
90
|
|
|
91
|
|
|
91
|
|
|
92
|
|
|
92
|
def get_encoding(obj):
|
|
93
|
def get_encoding(obj):
|
|
93
|
"""Get encoding for python source file defining obj
|
|
94
|
"""Get encoding for python source file defining obj
|
|
94
|
|
|
95
|
|
|
95
|
Returns None if obj is not defined in a sourcefile.
|
|
96
|
Returns None if obj is not defined in a sourcefile.
|
|
96
|
"""
|
|
97
|
"""
|
|
97
|
ofile = find_file(obj)
|
|
98
|
ofile = find_file(obj)
|
|
98
|
# run contents of file through pager starting at line where the object
|
|
99
|
# run contents of file through pager starting at line where the object
|
|
99
|
# is defined, as long as the file isn't binary and is actually on the
|
|
100
|
# is defined, as long as the file isn't binary and is actually on the
|
|
100
|
# filesystem.
|
|
101
|
# filesystem.
|
|
101
|
if ofile is None:
|
|
102
|
if ofile is None:
|
|
102
|
return None
|
|
103
|
return None
|
|
103
|
elif ofile.endswith(('.so', '.dll', '.pyd')):
|
|
104
|
elif ofile.endswith(('.so', '.dll', '.pyd')):
|
|
104
|
return None
|
|
105
|
return None
|
|
105
|
elif not os.path.isfile(ofile):
|
|
106
|
elif not os.path.isfile(ofile):
|
|
106
|
return None
|
|
107
|
return None
|
|
107
|
else:
|
|
108
|
else:
|
|
108
|
# Print only text files, not extension binaries. Note that
|
|
109
|
# Print only text files, not extension binaries. Note that
|
|
109
|
# getsourcelines returns lineno with 1-offset and page() uses
|
|
110
|
# getsourcelines returns lineno with 1-offset and page() uses
|
|
110
|
# 0-offset, so we must adjust.
|
|
111
|
# 0-offset, so we must adjust.
|
|
111
|
with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
|
|
112
|
with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
|
|
112
|
encoding, lines = openpy.detect_encoding(buffer.readline)
|
|
113
|
encoding, lines = openpy.detect_encoding(buffer.readline)
|
|
113
|
return encoding
|
|
114
|
return encoding
|
|
114
|
|
|
115
|
|
|
115
|
def getdoc(obj):
|
|
116
|
def getdoc(obj):
|
|
116
|
"""Stable wrapper around inspect.getdoc.
|
|
117
|
"""Stable wrapper around inspect.getdoc.
|
|
117
|
|
|
118
|
|
|
118
|
This can't crash because of attribute problems.
|
|
119
|
This can't crash because of attribute problems.
|
|
119
|
|
|
120
|
|
|
120
|
It also attempts to call a getdoc() method on the given object. This
|
|
121
|
It also attempts to call a getdoc() method on the given object. This
|
|
121
|
allows objects which provide their docstrings via non-standard mechanisms
|
|
122
|
allows objects which provide their docstrings via non-standard mechanisms
|
|
122
|
(like Pyro proxies) to still be inspected by ipython's ? system.
|
|
123
|
(like Pyro proxies) to still be inspected by ipython's ? system.
|
|
123
|
"""
|
|
124
|
"""
|
|
124
|
# Allow objects to offer customized documentation via a getdoc method:
|
|
125
|
# Allow objects to offer customized documentation via a getdoc method:
|
|
125
|
try:
|
|
126
|
try:
|
|
126
|
ds = obj.getdoc()
|
|
127
|
ds = obj.getdoc()
|
|
127
|
except Exception:
|
|
128
|
except Exception:
|
|
128
|
pass
|
|
129
|
pass
|
|
129
|
else:
|
|
130
|
else:
|
|
130
|
# if we get extra info, we add it to the normal docstring.
|
|
131
|
# if we get extra info, we add it to the normal docstring.
|
|
131
|
if isinstance(ds, string_types):
|
|
132
|
if isinstance(ds, string_types):
|
|
132
|
return inspect.cleandoc(ds)
|
|
133
|
return inspect.cleandoc(ds)
|
|
133
|
try:
|
|
134
|
try:
|
|
134
|
docstr = inspect.getdoc(obj)
|
|
135
|
docstr = inspect.getdoc(obj)
|
|
135
|
encoding = get_encoding(obj)
|
|
136
|
encoding = get_encoding(obj)
|
|
136
|
return py3compat.cast_unicode(docstr, encoding=encoding)
|
|
137
|
return py3compat.cast_unicode(docstr, encoding=encoding)
|
|
137
|
except Exception:
|
|
138
|
except Exception:
|
|
138
|
# Harden against an inspect failure, which can occur with
|
|
139
|
# Harden against an inspect failure, which can occur with
|
|
139
|
# extensions modules.
|
|
140
|
# extensions modules.
|
|
140
|
raise
|
|
141
|
raise
|
|
141
|
return None
|
|
142
|
return None
|
|
142
|
|
|
143
|
|
|
143
|
|
|
144
|
|
|
144
|
def getsource(obj, oname=''):
|
|
145
|
def getsource(obj, oname=''):
|
|
145
|
"""Wrapper around inspect.getsource.
|
|
146
|
"""Wrapper around inspect.getsource.
|
|
146
|
|
|
147
|
|
|
147
|
This can be modified by other projects to provide customized source
|
|
148
|
This can be modified by other projects to provide customized source
|
|
148
|
extraction.
|
|
149
|
extraction.
|
|
149
|
|
|
150
|
|
|
150
|
Parameters
|
|
151
|
Parameters
|
|
151
|
----------
|
|
152
|
----------
|
|
152
|
obj : object
|
|
153
|
obj : object
|
|
153
|
an object whose source code we will attempt to extract
|
|
154
|
an object whose source code we will attempt to extract
|
|
154
|
oname : str
|
|
155
|
oname : str
|
|
155
|
(optional) a name under which the object is known
|
|
156
|
(optional) a name under which the object is known
|
|
156
|
|
|
157
|
|
|
157
|
Returns
|
|
158
|
Returns
|
|
158
|
-------
|
|
159
|
-------
|
|
159
|
src : unicode or None
|
|
160
|
src : unicode or None
|
|
160
|
|
|
161
|
|
|
161
|
"""
|
|
162
|
"""
|
|
162
|
|
|
163
|
|
|
163
|
if isinstance(obj, property):
|
|
164
|
if isinstance(obj, property):
|
|
164
|
sources = []
|
|
165
|
sources = []
|
|
165
|
for attrname in ['fget', 'fset', 'fdel']:
|
|
166
|
for attrname in ['fget', 'fset', 'fdel']:
|
|
166
|
fn = getattr(obj, attrname)
|
|
167
|
fn = getattr(obj, attrname)
|
|
167
|
if fn is not None:
|
|
168
|
if fn is not None:
|
|
168
|
encoding = get_encoding(fn)
|
|
169
|
encoding = get_encoding(fn)
|
|
169
|
oname_prefix = ('%s.' % oname) if oname else ''
|
|
170
|
oname_prefix = ('%s.' % oname) if oname else ''
|
|
170
|
sources.append(cast_unicode(
|
|
171
|
sources.append(cast_unicode(
|
|
171
|
''.join(('# ', oname_prefix, attrname)),
|
|
172
|
''.join(('# ', oname_prefix, attrname)),
|
|
172
|
encoding=encoding))
|
|
173
|
encoding=encoding))
|
|
173
|
if inspect.isfunction(fn):
|
|
174
|
if inspect.isfunction(fn):
|
|
174
|
sources.append(dedent(getsource(fn)))
|
|
175
|
sources.append(dedent(getsource(fn)))
|
|
175
|
else:
|
|
176
|
else:
|
|
176
|
# Default str/repr only prints function name,
|
|
177
|
# Default str/repr only prints function name,
|
|
177
|
# pretty.pretty prints module name too.
|
|
178
|
# pretty.pretty prints module name too.
|
|
178
|
sources.append(cast_unicode(
|
|
179
|
sources.append(cast_unicode(
|
|
179
|
'%s%s = %s\n' % (
|
|
180
|
'%s%s = %s\n' % (
|
|
180
|
oname_prefix, attrname, pretty(fn)),
|
|
181
|
oname_prefix, attrname, pretty(fn)),
|
|
181
|
encoding=encoding))
|
|
182
|
encoding=encoding))
|
|
182
|
if sources:
|
|
183
|
if sources:
|
|
183
|
return '\n'.join(sources)
|
|
184
|
return '\n'.join(sources)
|
|
184
|
else:
|
|
185
|
else:
|
|
185
|
return None
|
|
186
|
return None
|
|
186
|
|
|
187
|
|
|
187
|
else:
|
|
188
|
else:
|
|
188
|
# Get source for non-property objects.
|
|
189
|
# Get source for non-property objects.
|
|
189
|
|
|
190
|
|
|
190
|
obj = _get_wrapped(obj)
|
|
191
|
obj = _get_wrapped(obj)
|
|
191
|
|
|
192
|
|
|
192
|
try:
|
|
193
|
try:
|
|
193
|
src = inspect.getsource(obj)
|
|
194
|
src = inspect.getsource(obj)
|
|
194
|
except TypeError:
|
|
195
|
except TypeError:
|
|
195
|
# The object itself provided no meaningful source, try looking for
|
|
196
|
# The object itself provided no meaningful source, try looking for
|
|
196
|
# its class definition instead.
|
|
197
|
# its class definition instead.
|
|
197
|
if hasattr(obj, '__class__'):
|
|
198
|
if hasattr(obj, '__class__'):
|
|
198
|
try:
|
|
199
|
try:
|
|
199
|
src = inspect.getsource(obj.__class__)
|
|
200
|
src = inspect.getsource(obj.__class__)
|
|
200
|
except TypeError:
|
|
201
|
except TypeError:
|
|
201
|
return None
|
|
202
|
return None
|
|
202
|
|
|
203
|
|
|
203
|
encoding = get_encoding(obj)
|
|
204
|
encoding = get_encoding(obj)
|
|
204
|
return cast_unicode(src, encoding=encoding)
|
|
205
|
return cast_unicode(src, encoding=encoding)
|
|
205
|
|
|
206
|
|
|
206
|
|
|
207
|
|
|
207
|
def is_simple_callable(obj):
|
|
208
|
def is_simple_callable(obj):
|
|
208
|
"""True if obj is a function ()"""
|
|
209
|
"""True if obj is a function ()"""
|
|
209
|
return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
|
|
210
|
return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
|
|
210
|
isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
|
|
211
|
isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
|
|
211
|
|
|
212
|
|
|
212
|
|
|
213
|
|
|
213
|
def getargspec(obj):
|
|
214
|
def getargspec(obj):
|
|
214
|
"""Wrapper around :func:`inspect.getfullargspec` on Python 3, and
|
|
215
|
"""Wrapper around :func:`inspect.getfullargspec` on Python 3, and
|
|
215
|
:func:inspect.getargspec` on Python 2.
|
|
216
|
:func:inspect.getargspec` on Python 2.
|
|
216
|
|
|
217
|
|
|
217
|
In addition to functions and methods, this can also handle objects with a
|
|
218
|
In addition to functions and methods, this can also handle objects with a
|
|
218
|
``__call__`` attribute.
|
|
219
|
``__call__`` attribute.
|
|
219
|
"""
|
|
220
|
"""
|
|
220
|
if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
|
|
221
|
if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
|
|
221
|
obj = obj.__call__
|
|
222
|
obj = obj.__call__
|
|
222
|
|
|
223
|
|
|
223
|
return inspect.getfullargspec(obj) if PY3 else inspect.getargspec(obj)
|
|
224
|
return inspect.getfullargspec(obj) if PY3 else inspect.getargspec(obj)
|
|
224
|
|
|
225
|
|
|
225
|
|
|
226
|
|
|
226
|
def format_argspec(argspec):
|
|
227
|
def format_argspec(argspec):
|
|
227
|
"""Format argspect, convenience wrapper around inspect's.
|
|
228
|
"""Format argspect, convenience wrapper around inspect's.
|
|
228
|
|
|
229
|
|
|
229
|
This takes a dict instead of ordered arguments and calls
|
|
230
|
This takes a dict instead of ordered arguments and calls
|
|
230
|
inspect.format_argspec with the arguments in the necessary order.
|
|
231
|
inspect.format_argspec with the arguments in the necessary order.
|
|
231
|
"""
|
|
232
|
"""
|
|
232
|
return inspect.formatargspec(argspec['args'], argspec['varargs'],
|
|
233
|
return inspect.formatargspec(argspec['args'], argspec['varargs'],
|
|
233
|
argspec['varkw'], argspec['defaults'])
|
|
234
|
argspec['varkw'], argspec['defaults'])
|
|
234
|
|
|
235
|
|
|
235
|
|
|
236
|
|
|
236
|
def call_tip(oinfo, format_call=True):
|
|
237
|
def call_tip(oinfo, format_call=True):
|
|
237
|
"""Extract call tip data from an oinfo dict.
|
|
238
|
"""Extract call tip data from an oinfo dict.
|
|
238
|
|
|
239
|
|
|
239
|
Parameters
|
|
240
|
Parameters
|
|
240
|
----------
|
|
241
|
----------
|
|
241
|
oinfo : dict
|
|
242
|
oinfo : dict
|
|
242
|
|
|
243
|
|
|
243
|
format_call : bool, optional
|
|
244
|
format_call : bool, optional
|
|
244
|
If True, the call line is formatted and returned as a string. If not, a
|
|
245
|
If True, the call line is formatted and returned as a string. If not, a
|
|
245
|
tuple of (name, argspec) is returned.
|
|
246
|
tuple of (name, argspec) is returned.
|
|
246
|
|
|
247
|
|
|
247
|
Returns
|
|
248
|
Returns
|
|
248
|
-------
|
|
249
|
-------
|
|
249
|
call_info : None, str or (str, dict) tuple.
|
|
250
|
call_info : None, str or (str, dict) tuple.
|
|
250
|
When format_call is True, the whole call information is formattted as a
|
|
251
|
When format_call is True, the whole call information is formattted as a
|
|
251
|
single string. Otherwise, the object's name and its argspec dict are
|
|
252
|
single string. Otherwise, the object's name and its argspec dict are
|
|
252
|
returned. If no call information is available, None is returned.
|
|
253
|
returned. If no call information is available, None is returned.
|
|
253
|
|
|
254
|
|
|
254
|
docstring : str or None
|
|
255
|
docstring : str or None
|
|
255
|
The most relevant docstring for calling purposes is returned, if
|
|
256
|
The most relevant docstring for calling purposes is returned, if
|
|
256
|
available. The priority is: call docstring for callable instances, then
|
|
257
|
available. The priority is: call docstring for callable instances, then
|
|
257
|
constructor docstring for classes, then main object's docstring otherwise
|
|
258
|
constructor docstring for classes, then main object's docstring otherwise
|
|
258
|
(regular functions).
|
|
259
|
(regular functions).
|
|
259
|
"""
|
|
260
|
"""
|
|
260
|
# Get call definition
|
|
261
|
# Get call definition
|
|
261
|
argspec = oinfo.get('argspec')
|
|
262
|
argspec = oinfo.get('argspec')
|
|
262
|
if argspec is None:
|
|
263
|
if argspec is None:
|
|
263
|
call_line = None
|
|
264
|
call_line = None
|
|
264
|
else:
|
|
265
|
else:
|
|
265
|
# Callable objects will have 'self' as their first argument, prune
|
|
266
|
# Callable objects will have 'self' as their first argument, prune
|
|
266
|
# it out if it's there for clarity (since users do *not* pass an
|
|
267
|
# it out if it's there for clarity (since users do *not* pass an
|
|
267
|
# extra first argument explicitly).
|
|
268
|
# extra first argument explicitly).
|
|
268
|
try:
|
|
269
|
try:
|
|
269
|
has_self = argspec['args'][0] == 'self'
|
|
270
|
has_self = argspec['args'][0] == 'self'
|
|
270
|
except (KeyError, IndexError):
|
|
271
|
except (KeyError, IndexError):
|
|
271
|
pass
|
|
272
|
pass
|
|
272
|
else:
|
|
273
|
else:
|
|
273
|
if has_self:
|
|
274
|
if has_self:
|
|
274
|
argspec['args'] = argspec['args'][1:]
|
|
275
|
argspec['args'] = argspec['args'][1:]
|
|
275
|
|
|
276
|
|
|
276
|
call_line = oinfo['name']+format_argspec(argspec)
|
|
277
|
call_line = oinfo['name']+format_argspec(argspec)
|
|
277
|
|
|
278
|
|
|
278
|
# Now get docstring.
|
|
279
|
# Now get docstring.
|
|
279
|
# The priority is: call docstring, constructor docstring, main one.
|
|
280
|
# The priority is: call docstring, constructor docstring, main one.
|
|
280
|
doc = oinfo.get('call_docstring')
|
|
281
|
doc = oinfo.get('call_docstring')
|
|
281
|
if doc is None:
|
|
282
|
if doc is None:
|
|
282
|
doc = oinfo.get('init_docstring')
|
|
283
|
doc = oinfo.get('init_docstring')
|
|
283
|
if doc is None:
|
|
284
|
if doc is None:
|
|
284
|
doc = oinfo.get('docstring','')
|
|
285
|
doc = oinfo.get('docstring','')
|
|
285
|
|
|
286
|
|
|
286
|
return call_line, doc
|
|
287
|
return call_line, doc
|
|
287
|
|
|
288
|
|
|
288
|
|
|
289
|
|
|
289
|
def _get_wrapped(obj):
|
|
290
|
def _get_wrapped(obj):
|
|
290
|
"""Get the original object if wrapped in one or more @decorators
|
|
291
|
"""Get the original object if wrapped in one or more @decorators
|
|
291
|
|
|
292
|
|
|
292
|
Some objects automatically construct similar objects on any unrecognised
|
|
293
|
Some objects automatically construct similar objects on any unrecognised
|
|
293
|
attribute access (e.g. unittest.mock.call). To protect against infinite loops,
|
|
294
|
attribute access (e.g. unittest.mock.call). To protect against infinite loops,
|
|
294
|
this will arbitrarily cut off after 100 levels of obj.__wrapped__
|
|
295
|
this will arbitrarily cut off after 100 levels of obj.__wrapped__
|
|
295
|
attribute access. --TK, Jan 2016
|
|
296
|
attribute access. --TK, Jan 2016
|
|
296
|
"""
|
|
297
|
"""
|
|
297
|
orig_obj = obj
|
|
298
|
orig_obj = obj
|
|
298
|
i = 0
|
|
299
|
i = 0
|
|
299
|
while safe_hasattr(obj, '__wrapped__'):
|
|
300
|
while safe_hasattr(obj, '__wrapped__'):
|
|
300
|
obj = obj.__wrapped__
|
|
301
|
obj = obj.__wrapped__
|
|
301
|
i += 1
|
|
302
|
i += 1
|
|
302
|
if i > 100:
|
|
303
|
if i > 100:
|
|
303
|
# __wrapped__ is probably a lie, so return the thing we started with
|
|
304
|
# __wrapped__ is probably a lie, so return the thing we started with
|
|
304
|
return orig_obj
|
|
305
|
return orig_obj
|
|
305
|
return obj
|
|
306
|
return obj
|
|
306
|
|
|
307
|
|
|
307
|
def find_file(obj):
|
|
308
|
def find_file(obj):
|
|
308
|
"""Find the absolute path to the file where an object was defined.
|
|
309
|
"""Find the absolute path to the file where an object was defined.
|
|
309
|
|
|
310
|
|
|
310
|
This is essentially a robust wrapper around `inspect.getabsfile`.
|
|
311
|
This is essentially a robust wrapper around `inspect.getabsfile`.
|
|
311
|
|
|
312
|
|
|
312
|
Returns None if no file can be found.
|
|
313
|
Returns None if no file can be found.
|
|
313
|
|
|
314
|
|
|
314
|
Parameters
|
|
315
|
Parameters
|
|
315
|
----------
|
|
316
|
----------
|
|
316
|
obj : any Python object
|
|
317
|
obj : any Python object
|
|
317
|
|
|
318
|
|
|
318
|
Returns
|
|
319
|
Returns
|
|
319
|
-------
|
|
320
|
-------
|
|
320
|
fname : str
|
|
321
|
fname : str
|
|
321
|
The absolute path to the file where the object was defined.
|
|
322
|
The absolute path to the file where the object was defined.
|
|
322
|
"""
|
|
323
|
"""
|
|
323
|
obj = _get_wrapped(obj)
|
|
324
|
obj = _get_wrapped(obj)
|
|
324
|
|
|
325
|
|
|
325
|
fname = None
|
|
326
|
fname = None
|
|
326
|
try:
|
|
327
|
try:
|
|
327
|
fname = inspect.getabsfile(obj)
|
|
328
|
fname = inspect.getabsfile(obj)
|
|
328
|
except TypeError:
|
|
329
|
except TypeError:
|
|
329
|
# For an instance, the file that matters is where its class was
|
|
330
|
# For an instance, the file that matters is where its class was
|
|
330
|
# declared.
|
|
331
|
# declared.
|
|
331
|
if hasattr(obj, '__class__'):
|
|
332
|
if hasattr(obj, '__class__'):
|
|
332
|
try:
|
|
333
|
try:
|
|
333
|
fname = inspect.getabsfile(obj.__class__)
|
|
334
|
fname = inspect.getabsfile(obj.__class__)
|
|
334
|
except TypeError:
|
|
335
|
except TypeError:
|
|
335
|
# Can happen for builtins
|
|
336
|
# Can happen for builtins
|
|
336
|
pass
|
|
337
|
pass
|
|
337
|
except:
|
|
338
|
except:
|
|
338
|
pass
|
|
339
|
pass
|
|
339
|
return cast_unicode(fname)
|
|
340
|
return cast_unicode(fname)
|
|
340
|
|
|
341
|
|
|
341
|
|
|
342
|
|
|
342
|
def find_source_lines(obj):
|
|
343
|
def find_source_lines(obj):
|
|
343
|
"""Find the line number in a file where an object was defined.
|
|
344
|
"""Find the line number in a file where an object was defined.
|
|
344
|
|
|
345
|
|
|
345
|
This is essentially a robust wrapper around `inspect.getsourcelines`.
|
|
346
|
This is essentially a robust wrapper around `inspect.getsourcelines`.
|
|
346
|
|
|
347
|
|
|
347
|
Returns None if no file can be found.
|
|
348
|
Returns None if no file can be found.
|
|
348
|
|
|
349
|
|
|
349
|
Parameters
|
|
350
|
Parameters
|
|
350
|
----------
|
|
351
|
----------
|
|
351
|
obj : any Python object
|
|
352
|
obj : any Python object
|
|
352
|
|
|
353
|
|
|
353
|
Returns
|
|
354
|
Returns
|
|
354
|
-------
|
|
355
|
-------
|
|
355
|
lineno : int
|
|
356
|
lineno : int
|
|
356
|
The line number where the object definition starts.
|
|
357
|
The line number where the object definition starts.
|
|
357
|
"""
|
|
358
|
"""
|
|
358
|
obj = _get_wrapped(obj)
|
|
359
|
obj = _get_wrapped(obj)
|
|
359
|
|
|
360
|
|
|
360
|
try:
|
|
361
|
try:
|
|
361
|
try:
|
|
362
|
try:
|
|
362
|
lineno = inspect.getsourcelines(obj)[1]
|
|
363
|
lineno = inspect.getsourcelines(obj)[1]
|
|
363
|
except TypeError:
|
|
364
|
except TypeError:
|
|
364
|
# For instances, try the class object like getsource() does
|
|
365
|
# For instances, try the class object like getsource() does
|
|
365
|
if hasattr(obj, '__class__'):
|
|
366
|
if hasattr(obj, '__class__'):
|
|
366
|
lineno = inspect.getsourcelines(obj.__class__)[1]
|
|
367
|
lineno = inspect.getsourcelines(obj.__class__)[1]
|
|
367
|
else:
|
|
368
|
else:
|
|
368
|
lineno = None
|
|
369
|
lineno = None
|
|
369
|
except:
|
|
370
|
except:
|
|
370
|
return None
|
|
371
|
return None
|
|
371
|
|
|
372
|
|
|
372
|
return lineno
|
|
373
|
return lineno
|
|
373
|
|
|
374
|
|
|
374
|
class Inspector(Colorable):
|
|
375
|
class Inspector(Colorable):
|
|
375
|
|
|
376
|
|
|
376
|
def __init__(self, color_table=InspectColors,
|
|
377
|
def __init__(self, color_table=InspectColors,
|
|
377
|
code_color_table=PyColorize.ANSICodeColors,
|
|
378
|
code_color_table=PyColorize.ANSICodeColors,
|
|
378
|
scheme='NoColor',
|
|
379
|
scheme='NoColor',
|
|
379
|
str_detail_level=0,
|
|
380
|
str_detail_level=0,
|
|
380
|
parent=None, config=None):
|
|
381
|
parent=None, config=None):
|
|
381
|
super(Inspector, self).__init__(parent=parent, config=config)
|
|
382
|
super(Inspector, self).__init__(parent=parent, config=config)
|
|
382
|
self.color_table = color_table
|
|
383
|
self.color_table = color_table
|
|
383
|
self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
|
|
384
|
self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
|
|
384
|
self.format = self.parser.format
|
|
385
|
self.format = self.parser.format
|
|
385
|
self.str_detail_level = str_detail_level
|
|
386
|
self.str_detail_level = str_detail_level
|
|
386
|
self.set_active_scheme(scheme)
|
|
387
|
self.set_active_scheme(scheme)
|
|
387
|
|
|
388
|
|
|
388
|
def _getdef(self,obj,oname=''):
|
|
389
|
def _getdef(self,obj,oname=''):
|
|
389
|
"""Return the call signature for any callable object.
|
|
390
|
"""Return the call signature for any callable object.
|
|
390
|
|
|
391
|
|
|
391
|
If any exception is generated, None is returned instead and the
|
|
392
|
If any exception is generated, None is returned instead and the
|
|
392
|
exception is suppressed."""
|
|
393
|
exception is suppressed."""
|
|
393
|
try:
|
|
394
|
try:
|
|
394
|
hdef = oname + str(signature(obj))
|
|
395
|
hdef = oname + str(signature(obj))
|
|
395
|
return cast_unicode(hdef)
|
|
396
|
return cast_unicode(hdef)
|
|
396
|
except:
|
|
397
|
except:
|
|
397
|
return None
|
|
398
|
return None
|
|
398
|
|
|
399
|
|
|
399
|
def __head(self,h):
|
|
400
|
def __head(self,h):
|
|
400
|
"""Return a header string with proper colors."""
|
|
401
|
"""Return a header string with proper colors."""
|
|
401
|
return '%s%s%s' % (self.color_table.active_colors.header,h,
|
|
402
|
return '%s%s%s' % (self.color_table.active_colors.header,h,
|
|
402
|
self.color_table.active_colors.normal)
|
|
403
|
self.color_table.active_colors.normal)
|
|
403
|
|
|
404
|
|
|
404
|
def set_active_scheme(self, scheme):
|
|
405
|
def set_active_scheme(self, scheme):
|
|
405
|
self.color_table.set_active_scheme(scheme)
|
|
406
|
self.color_table.set_active_scheme(scheme)
|
|
406
|
self.parser.color_table.set_active_scheme(scheme)
|
|
407
|
self.parser.color_table.set_active_scheme(scheme)
|
|
407
|
|
|
408
|
|
|
408
|
def noinfo(self, msg, oname):
|
|
409
|
def noinfo(self, msg, oname):
|
|
409
|
"""Generic message when no information is found."""
|
|
410
|
"""Generic message when no information is found."""
|
|
410
|
print('No %s found' % msg, end=' ')
|
|
411
|
print('No %s found' % msg, end=' ')
|
|
411
|
if oname:
|
|
412
|
if oname:
|
|
412
|
print('for %s' % oname)
|
|
413
|
print('for %s' % oname)
|
|
413
|
else:
|
|
414
|
else:
|
|
414
|
print()
|
|
415
|
print()
|
|
415
|
|
|
416
|
|
|
416
|
def pdef(self, obj, oname=''):
|
|
417
|
def pdef(self, obj, oname=''):
|
|
417
|
"""Print the call signature for any callable object.
|
|
418
|
"""Print the call signature for any callable object.
|
|
418
|
|
|
419
|
|
|
419
|
If the object is a class, print the constructor information."""
|
|
420
|
If the object is a class, print the constructor information."""
|
|
420
|
|
|
421
|
|
|
421
|
if not callable(obj):
|
|
422
|
if not callable(obj):
|
|
422
|
print('Object is not callable.')
|
|
423
|
print('Object is not callable.')
|
|
423
|
return
|
|
424
|
return
|
|
424
|
|
|
425
|
|
|
425
|
header = ''
|
|
426
|
header = ''
|
|
426
|
|
|
427
|
|
|
427
|
if inspect.isclass(obj):
|
|
428
|
if inspect.isclass(obj):
|
|
428
|
header = self.__head('Class constructor information:\n')
|
|
429
|
header = self.__head('Class constructor information:\n')
|
|
429
|
elif (not py3compat.PY3) and type(obj) is types.InstanceType:
|
|
430
|
elif (not py3compat.PY3) and type(obj) is types.InstanceType:
|
|
430
|
obj = obj.__call__
|
|
431
|
obj = obj.__call__
|
|
431
|
|
|
432
|
|
|
432
|
output = self._getdef(obj,oname)
|
|
433
|
output = self._getdef(obj,oname)
|
|
433
|
if output is None:
|
|
434
|
if output is None:
|
|
434
|
self.noinfo('definition header',oname)
|
|
435
|
self.noinfo('definition header',oname)
|
|
435
|
else:
|
|
436
|
else:
|
|
436
|
print(header,self.format(output), end=' ')
|
|
437
|
print(header,self.format(output), end=' ')
|
|
437
|
|
|
438
|
|
|
|
|
|
439
|
# In Python 3, all classes are new-style, so they all have __init__.
|
|
|
|
|
440
|
@skip_doctest
|
|
|
|
|
441
|
def pdoc(self, obj, oname='', formatter=None):
|
|
|
|
|
442
|
"""Print the docstring for any object.
|
|
|
|
|
443
|
|
|
|
|
|
444
|
Optional:
|
|
|
|
|
445
|
-formatter: a function to run the docstring through for specially
|
|
|
|
|
446
|
formatted docstrings.
|
|
|
|
|
447
|
|
|
|
|
|
448
|
Examples
|
|
|
|
|
449
|
--------
|
|
|
|
|
450
|
|
|
|
|
|
451
|
In [1]: class NoInit:
|
|
|
|
|
452
|
...: pass
|
|
|
|
|
453
|
|
|
|
|
|
454
|
In [2]: class NoDoc:
|
|
|
|
|
455
|
...: def __init__(self):
|
|
|
|
|
456
|
...: pass
|
|
|
|
|
457
|
|
|
|
|
|
458
|
In [3]: %pdoc NoDoc
|
|
|
|
|
459
|
No documentation found for NoDoc
|
|
|
|
|
460
|
|
|
|
|
|
461
|
In [4]: %pdoc NoInit
|
|
|
|
|
462
|
No documentation found for NoInit
|
|
|
|
|
463
|
|
|
|
|
|
464
|
In [5]: obj = NoInit()
|
|
|
|
|
465
|
|
|
|
|
|
466
|
In [6]: %pdoc obj
|
|
|
|
|
467
|
No documentation found for obj
|
|
|
|
|
468
|
|
|
|
|
|
469
|
In [5]: obj2 = NoDoc()
|
|
|
|
|
470
|
|
|
|
|
|
471
|
In [6]: %pdoc obj2
|
|
|
|
|
472
|
No documentation found for obj2
|
|
|
|
|
473
|
"""
|
|
|
|
|
474
|
|
|
|
|
|
475
|
head = self.__head # For convenience
|
|
|
|
|
476
|
lines = []
|
|
|
|
|
477
|
ds = getdoc(obj)
|
|
|
|
|
478
|
if formatter:
|
|
|
|
|
479
|
ds = formatter(ds).get('plain/text', ds)
|
|
|
|
|
480
|
if ds:
|
|
|
|
|
481
|
lines.append(head("Class docstring:"))
|
|
|
|
|
482
|
lines.append(indent(ds))
|
|
|
|
|
483
|
if inspect.isclass(obj) and hasattr(obj, '__init__'):
|
|
|
|
|
484
|
init_ds = getdoc(obj.__init__)
|
|
|
|
|
485
|
if init_ds is not None:
|
|
|
|
|
486
|
lines.append(head("Init docstring:"))
|
|
|
|
|
487
|
lines.append(indent(init_ds))
|
|
|
|
|
488
|
elif hasattr(obj,'__call__'):
|
|
|
|
|
489
|
call_ds = getdoc(obj.__call__)
|
|
|
|
|
490
|
if call_ds:
|
|
|
|
|
491
|
lines.append(head("Call docstring:"))
|
|
|
|
|
492
|
lines.append(indent(call_ds))
|
|
|
|
|
493
|
|
|
|
|
|
494
|
if not lines:
|
|
|
|
|
495
|
self.noinfo('documentation',oname)
|
|
|
|
|
496
|
else:
|
|
|
|
|
497
|
page.page('\n'.join(lines))
|
|
|
|
|
498
|
|
|
438
|
def psource(self, obj, oname=''):
|
|
499
|
def psource(self, obj, oname=''):
|
|
439
|
"""Print the source code for an object."""
|
|
500
|
"""Print the source code for an object."""
|
|
440
|
|
|
501
|
|
|
441
|
# Flush the source cache because inspect can return out-of-date source
|
|
502
|
# Flush the source cache because inspect can return out-of-date source
|
|
442
|
linecache.checkcache()
|
|
503
|
linecache.checkcache()
|
|
443
|
try:
|
|
504
|
try:
|
|
444
|
src = getsource(obj, oname=oname)
|
|
505
|
src = getsource(obj, oname=oname)
|
|
445
|
except Exception:
|
|
506
|
except Exception:
|
|
446
|
src = None
|
|
507
|
src = None
|
|
447
|
|
|
508
|
|
|
448
|
if src is None:
|
|
509
|
if src is None:
|
|
449
|
self.noinfo('source', oname)
|
|
510
|
self.noinfo('source', oname)
|
|
450
|
else:
|
|
511
|
else:
|
|
451
|
page.page(self.format(src))
|
|
512
|
page.page(self.format(src))
|
|
452
|
|
|
513
|
|
|
453
|
def pfile(self, obj, oname=''):
|
|
514
|
def pfile(self, obj, oname=''):
|
|
454
|
"""Show the whole file where an object was defined."""
|
|
515
|
"""Show the whole file where an object was defined."""
|
|
455
|
|
|
516
|
|
|
456
|
lineno = find_source_lines(obj)
|
|
517
|
lineno = find_source_lines(obj)
|
|
457
|
if lineno is None:
|
|
518
|
if lineno is None:
|
|
458
|
self.noinfo('file', oname)
|
|
519
|
self.noinfo('file', oname)
|
|
459
|
return
|
|
520
|
return
|
|
460
|
|
|
521
|
|
|
461
|
ofile = find_file(obj)
|
|
522
|
ofile = find_file(obj)
|
|
462
|
# run contents of file through pager starting at line where the object
|
|
523
|
# run contents of file through pager starting at line where the object
|
|
463
|
# is defined, as long as the file isn't binary and is actually on the
|
|
524
|
# is defined, as long as the file isn't binary and is actually on the
|
|
464
|
# filesystem.
|
|
525
|
# filesystem.
|
|
465
|
if ofile.endswith(('.so', '.dll', '.pyd')):
|
|
526
|
if ofile.endswith(('.so', '.dll', '.pyd')):
|
|
466
|
print('File %r is binary, not printing.' % ofile)
|
|
527
|
print('File %r is binary, not printing.' % ofile)
|
|
467
|
elif not os.path.isfile(ofile):
|
|
528
|
elif not os.path.isfile(ofile):
|
|
468
|
print('File %r does not exist, not printing.' % ofile)
|
|
529
|
print('File %r does not exist, not printing.' % ofile)
|
|
469
|
else:
|
|
530
|
else:
|
|
470
|
# Print only text files, not extension binaries. Note that
|
|
531
|
# Print only text files, not extension binaries. Note that
|
|
471
|
# getsourcelines returns lineno with 1-offset and page() uses
|
|
532
|
# getsourcelines returns lineno with 1-offset and page() uses
|
|
472
|
# 0-offset, so we must adjust.
|
|
533
|
# 0-offset, so we must adjust.
|
|
473
|
page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
|
|
534
|
page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
|
|
474
|
|
|
535
|
|
|
475
|
def _format_fields(self, fields, title_width=0):
|
|
536
|
def _format_fields(self, fields, title_width=0):
|
|
476
|
"""Formats a list of fields for display.
|
|
537
|
"""Formats a list of fields for display.
|
|
477
|
|
|
538
|
|
|
478
|
Parameters
|
|
539
|
Parameters
|
|
479
|
----------
|
|
540
|
----------
|
|
480
|
fields : list
|
|
541
|
fields : list
|
|
481
|
A list of 2-tuples: (field_title, field_content)
|
|
542
|
A list of 2-tuples: (field_title, field_content)
|
|
482
|
title_width : int
|
|
543
|
title_width : int
|
|
483
|
How many characters to pad titles to. Default to longest title.
|
|
544
|
How many characters to pad titles to. Default to longest title.
|
|
484
|
"""
|
|
545
|
"""
|
|
485
|
out = []
|
|
546
|
out = []
|
|
486
|
header = self.__head
|
|
547
|
header = self.__head
|
|
487
|
if title_width == 0:
|
|
548
|
if title_width == 0:
|
|
488
|
title_width = max(len(title) + 2 for title, _ in fields)
|
|
549
|
title_width = max(len(title) + 2 for title, _ in fields)
|
|
489
|
for title, content in fields:
|
|
550
|
for title, content in fields:
|
|
490
|
if len(content.splitlines()) > 1:
|
|
551
|
if len(content.splitlines()) > 1:
|
|
491
|
title = header(title + ':') + '\n'
|
|
552
|
title = header(title + ':') + '\n'
|
|
492
|
else:
|
|
553
|
else:
|
|
493
|
title = header((title + ':').ljust(title_width))
|
|
554
|
title = header((title + ':').ljust(title_width))
|
|
494
|
out.append(cast_unicode(title) + cast_unicode(content))
|
|
555
|
out.append(cast_unicode(title) + cast_unicode(content))
|
|
495
|
return "\n".join(out)
|
|
556
|
return "\n".join(out)
|
|
496
|
|
|
557
|
|
|
497
|
def _mime_format(self, text, formatter=None):
|
|
558
|
def _mime_format(self, text, formatter=None):
|
|
498
|
"""Return a mime bundle representation of the input text.
|
|
559
|
"""Return a mime bundle representation of the input text.
|
|
499
|
|
|
560
|
|
|
500
|
- if `formatter` is None, the returned mime bundle has
|
|
561
|
- if `formatter` is None, the returned mime bundle has
|
|
501
|
a `text/plain` field, with the input text.
|
|
562
|
a `text/plain` field, with the input text.
|
|
502
|
a `text/html` field with a `<pre>` tag containing the input text.
|
|
563
|
a `text/html` field with a `<pre>` tag containing the input text.
|
|
503
|
|
|
564
|
|
|
504
|
- if `formatter` is not None, it must be a callable transforming the
|
|
565
|
- if `formatter` is not None, it must be a callable transforming the
|
|
505
|
input text into a mime bundle. Default values for `text/plain` and
|
|
566
|
input text into a mime bundle. Default values for `text/plain` and
|
|
506
|
`text/html` representations are the ones described above.
|
|
567
|
`text/html` representations are the ones described above.
|
|
507
|
|
|
568
|
|
|
508
|
Note:
|
|
569
|
Note:
|
|
509
|
|
|
570
|
|
|
510
|
Formatters returning strings are supported but this behavior is deprecated.
|
|
571
|
Formatters returning strings are supported but this behavior is deprecated.
|
|
511
|
|
|
572
|
|
|
512
|
"""
|
|
573
|
"""
|
|
513
|
text = cast_unicode(text)
|
|
574
|
text = cast_unicode(text)
|
|
514
|
defaults = {
|
|
575
|
defaults = {
|
|
515
|
'text/plain': text,
|
|
576
|
'text/plain': text,
|
|
516
|
'text/html': '<pre>' + text + '</pre>'
|
|
577
|
'text/html': '<pre>' + text + '</pre>'
|
|
517
|
}
|
|
578
|
}
|
|
518
|
|
|
579
|
|
|
519
|
if formatter is None:
|
|
580
|
if formatter is None:
|
|
520
|
return defaults
|
|
581
|
return defaults
|
|
521
|
else:
|
|
582
|
else:
|
|
522
|
formatted = formatter(text)
|
|
583
|
formatted = formatter(text)
|
|
523
|
|
|
584
|
|
|
524
|
if not isinstance(formatted, dict):
|
|
585
|
if not isinstance(formatted, dict):
|
|
525
|
# Handle the deprecated behavior of a formatter returning
|
|
586
|
# Handle the deprecated behavior of a formatter returning
|
|
526
|
# a string instead of a mime bundle.
|
|
587
|
# a string instead of a mime bundle.
|
|
527
|
return {
|
|
588
|
return {
|
|
528
|
'text/plain': formatted,
|
|
589
|
'text/plain': formatted,
|
|
529
|
'text/html': '<pre>' + formatted + '</pre>'
|
|
590
|
'text/html': '<pre>' + formatted + '</pre>'
|
|
530
|
}
|
|
591
|
}
|
|
531
|
|
|
592
|
|
|
532
|
else:
|
|
593
|
else:
|
|
533
|
return dict(defaults, **formatted)
|
|
594
|
return dict(defaults, **formatted)
|
|
534
|
|
|
595
|
|
|
535
|
|
|
596
|
|
|
536
|
def format_mime(self, bundle):
|
|
597
|
def format_mime(self, bundle):
|
|
537
|
|
|
598
|
|
|
538
|
text_plain = bundle['text/plain']
|
|
599
|
text_plain = bundle['text/plain']
|
|
539
|
|
|
600
|
|
|
540
|
text = ''
|
|
601
|
text = ''
|
|
541
|
heads, bodies = list(zip(*text_plain))
|
|
602
|
heads, bodies = list(zip(*text_plain))
|
|
542
|
_len = max(len(h) for h in heads)
|
|
603
|
_len = max(len(h) for h in heads)
|
|
543
|
|
|
604
|
|
|
544
|
for head, body in zip(heads, bodies):
|
|
605
|
for head, body in zip(heads, bodies):
|
|
545
|
body = body.strip('\n')
|
|
606
|
body = body.strip('\n')
|
|
546
|
delim = '\n' if '\n' in body else ' '
|
|
607
|
delim = '\n' if '\n' in body else ' '
|
|
547
|
text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
|
|
608
|
text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
|
|
548
|
|
|
609
|
|
|
549
|
bundle['text/plain'] = text
|
|
610
|
bundle['text/plain'] = text
|
|
550
|
return bundle
|
|
611
|
return bundle
|
|
551
|
|
|
612
|
|
|
552
|
def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
|
|
613
|
def _get_info(self, obj, oname='', formatter=None, info=None, detail_level=0):
|
|
553
|
"""Retrieve an info dict and format it."""
|
|
614
|
"""Retrieve an info dict and format it."""
|
|
554
|
|
|
615
|
|
|
555
|
info = self._info(obj, oname=oname, info=info, detail_level=detail_level)
|
|
616
|
info = self._info(obj, oname=oname, info=info, detail_level=detail_level)
|
|
556
|
|
|
617
|
|
|
557
|
_mime = {
|
|
618
|
_mime = {
|
|
558
|
'text/plain': [],
|
|
619
|
'text/plain': [],
|
|
559
|
'text/html': '',
|
|
620
|
'text/html': '',
|
|
560
|
}
|
|
621
|
}
|
|
561
|
|
|
622
|
|
|
562
|
def append_field(bundle, title, key, formatter=None):
|
|
623
|
def append_field(bundle, title, key, formatter=None):
|
|
563
|
field = info[key]
|
|
624
|
field = info[key]
|
|
564
|
if field is not None:
|
|
625
|
if field is not None:
|
|
565
|
formatted_field = self._mime_format(field, formatter)
|
|
626
|
formatted_field = self._mime_format(field, formatter)
|
|
566
|
bundle['text/plain'].append((title, formatted_field['text/plain']))
|
|
627
|
bundle['text/plain'].append((title, formatted_field['text/plain']))
|
|
567
|
bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
|
|
628
|
bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
|
|
568
|
|
|
|