##// END OF EJS Templates
MAINT: refactor/please mypy....
Matthias Bussonnier -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,41 +1,38 b''
1 name: Run MyPy
1 name: Run MyPy
2
2
3 on:
3 on:
4 push:
4 push:
5 branches: [ main, 7.x]
5 branches: [ main, 7.x]
6 pull_request:
6 pull_request:
7 branches: [ main, 7.x]
7 branches: [ main, 7.x]
8
8
9 permissions:
9 permissions:
10 contents: read
10 contents: read
11
11
12 jobs:
12 jobs:
13 build:
13 build:
14
14
15 runs-on: ubuntu-latest
15 runs-on: ubuntu-latest
16 strategy:
16 strategy:
17 matrix:
17 matrix:
18 python-version: ["3.x"]
18 python-version: ["3.x"]
19
19
20 steps:
20 steps:
21 - uses: actions/checkout@v3
21 - uses: actions/checkout@v3
22 - name: Set up Python ${{ matrix.python-version }}
22 - name: Set up Python ${{ matrix.python-version }}
23 uses: actions/setup-python@v4
23 uses: actions/setup-python@v4
24 with:
24 with:
25 python-version: ${{ matrix.python-version }}
25 python-version: ${{ matrix.python-version }}
26 - name: Install dependencies
26 - name: Install dependencies
27 run: |
27 run: |
28 python -m pip install --upgrade pip
28 python -m pip install --upgrade pip
29 pip install mypy pyflakes flake8
29 pip install mypy pyflakes flake8 types-decorator
30 - name: Lint with mypy
30 - name: Lint with mypy
31 run: |
31 run: |
32 set -e
32 set -e
33 mypy -p IPython.terminal
33 mypy IPython
34 mypy -p IPython.core.magics
35 mypy -p IPython.core.guarded_eval
36 mypy -p IPython.core.completer
37 - name: Lint with pyflakes
34 - name: Lint with pyflakes
38 run: |
35 run: |
39 set -e
36 set -e
40 flake8 IPython/core/magics/script.py
37 flake8 IPython/core/magics/script.py
41 flake8 IPython/core/magics/packaging.py
38 flake8 IPython/core/magics/packaging.py
@@ -1,1026 +1,1028 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
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 import abc
13 import abc
14 import sys
14 import sys
15 import traceback
15 import traceback
16 import warnings
16 import warnings
17 from io import StringIO
17 from io import StringIO
18
18
19 from decorator import decorator
19 from decorator import decorator
20
20
21 from traitlets.config.configurable import Configurable
21 from traitlets.config.configurable import Configurable
22 from .getipython import get_ipython
22 from .getipython import get_ipython
23 from ..utils.sentinel import Sentinel
23 from ..utils.sentinel import Sentinel
24 from ..utils.dir2 import get_real_method
24 from ..utils.dir2 import get_real_method
25 from ..lib import pretty
25 from ..lib import pretty
26 from traitlets import (
26 from traitlets import (
27 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
27 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
28 ForwardDeclaredInstance,
28 ForwardDeclaredInstance,
29 default, observe,
29 default, observe,
30 )
30 )
31
31
32 from typing import Any
33
32
34
33 class DisplayFormatter(Configurable):
35 class DisplayFormatter(Configurable):
34
36
35 active_types = List(Unicode(),
37 active_types = List(Unicode(),
36 help="""List of currently active mime-types to display.
38 help="""List of currently active mime-types to display.
37 You can use this to set a white-list for formats to display.
39 You can use this to set a white-list for formats to display.
38
40
39 Most users will not need to change this value.
41 Most users will not need to change this value.
40 """).tag(config=True)
42 """).tag(config=True)
41
43
42 @default('active_types')
44 @default('active_types')
43 def _active_types_default(self):
45 def _active_types_default(self):
44 return self.format_types
46 return self.format_types
45
47
46 @observe('active_types')
48 @observe('active_types')
47 def _active_types_changed(self, change):
49 def _active_types_changed(self, change):
48 for key, formatter in self.formatters.items():
50 for key, formatter in self.formatters.items():
49 if key in change['new']:
51 if key in change['new']:
50 formatter.enabled = True
52 formatter.enabled = True
51 else:
53 else:
52 formatter.enabled = False
54 formatter.enabled = False
53
55
54 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
56 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
55 @default('ipython_display_formatter')
57 @default('ipython_display_formatter')
56 def _default_formatter(self):
58 def _default_formatter(self):
57 return IPythonDisplayFormatter(parent=self)
59 return IPythonDisplayFormatter(parent=self)
58
60
59 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
61 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
60 @default('mimebundle_formatter')
62 @default('mimebundle_formatter')
61 def _default_mime_formatter(self):
63 def _default_mime_formatter(self):
62 return MimeBundleFormatter(parent=self)
64 return MimeBundleFormatter(parent=self)
63
65
64 # A dict of formatter whose keys are format types (MIME types) and whose
66 # A dict of formatter whose keys are format types (MIME types) and whose
65 # values are subclasses of BaseFormatter.
67 # values are subclasses of BaseFormatter.
66 formatters = Dict()
68 formatters = Dict()
67 @default('formatters')
69 @default('formatters')
68 def _formatters_default(self):
70 def _formatters_default(self):
69 """Activate the default formatters."""
71 """Activate the default formatters."""
70 formatter_classes = [
72 formatter_classes = [
71 PlainTextFormatter,
73 PlainTextFormatter,
72 HTMLFormatter,
74 HTMLFormatter,
73 MarkdownFormatter,
75 MarkdownFormatter,
74 SVGFormatter,
76 SVGFormatter,
75 PNGFormatter,
77 PNGFormatter,
76 PDFFormatter,
78 PDFFormatter,
77 JPEGFormatter,
79 JPEGFormatter,
78 LatexFormatter,
80 LatexFormatter,
79 JSONFormatter,
81 JSONFormatter,
80 JavascriptFormatter
82 JavascriptFormatter
81 ]
83 ]
82 d = {}
84 d = {}
83 for cls in formatter_classes:
85 for cls in formatter_classes:
84 f = cls(parent=self)
86 f = cls(parent=self)
85 d[f.format_type] = f
87 d[f.format_type] = f
86 return d
88 return d
87
89
88 def format(self, obj, include=None, exclude=None):
90 def format(self, obj, include=None, exclude=None):
89 """Return a format data dict for an object.
91 """Return a format data dict for an object.
90
92
91 By default all format types will be computed.
93 By default all format types will be computed.
92
94
93 The following MIME types are usually implemented:
95 The following MIME types are usually implemented:
94
96
95 * text/plain
97 * text/plain
96 * text/html
98 * text/html
97 * text/markdown
99 * text/markdown
98 * text/latex
100 * text/latex
99 * application/json
101 * application/json
100 * application/javascript
102 * application/javascript
101 * application/pdf
103 * application/pdf
102 * image/png
104 * image/png
103 * image/jpeg
105 * image/jpeg
104 * image/svg+xml
106 * image/svg+xml
105
107
106 Parameters
108 Parameters
107 ----------
109 ----------
108 obj : object
110 obj : object
109 The Python object whose format data will be computed.
111 The Python object whose format data will be computed.
110 include : list, tuple or set; optional
112 include : list, tuple or set; optional
111 A list of format type strings (MIME types) to include in the
113 A list of format type strings (MIME types) to include in the
112 format data dict. If this is set *only* the format types included
114 format data dict. If this is set *only* the format types included
113 in this list will be computed.
115 in this list will be computed.
114 exclude : list, tuple or set; optional
116 exclude : list, tuple or set; optional
115 A list of format type string (MIME types) to exclude in the format
117 A list of format type string (MIME types) to exclude in the format
116 data dict. If this is set all format types will be computed,
118 data dict. If this is set all format types will be computed,
117 except for those included in this argument.
119 except for those included in this argument.
118 Mimetypes present in exclude will take precedence over the ones in include
120 Mimetypes present in exclude will take precedence over the ones in include
119
121
120 Returns
122 Returns
121 -------
123 -------
122 (format_dict, metadata_dict) : tuple of two dicts
124 (format_dict, metadata_dict) : tuple of two dicts
123 format_dict is a dictionary of key/value pairs, one of each format that was
125 format_dict is a dictionary of key/value pairs, one of each format that was
124 generated for the object. The keys are the format types, which
126 generated for the object. The keys are the format types, which
125 will usually be MIME type strings and the values and JSON'able
127 will usually be MIME type strings and the values and JSON'able
126 data structure containing the raw data for the representation in
128 data structure containing the raw data for the representation in
127 that format.
129 that format.
128
130
129 metadata_dict is a dictionary of metadata about each mime-type output.
131 metadata_dict is a dictionary of metadata about each mime-type output.
130 Its keys will be a strict subset of the keys in format_dict.
132 Its keys will be a strict subset of the keys in format_dict.
131
133
132 Notes
134 Notes
133 -----
135 -----
134 If an object implement `_repr_mimebundle_` as well as various
136 If an object implement `_repr_mimebundle_` as well as various
135 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
137 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
136 precedence and the corresponding `_repr_*_` for this mimetype will
138 precedence and the corresponding `_repr_*_` for this mimetype will
137 not be called.
139 not be called.
138
140
139 """
141 """
140 format_dict = {}
142 format_dict = {}
141 md_dict = {}
143 md_dict = {}
142
144
143 if self.ipython_display_formatter(obj):
145 if self.ipython_display_formatter(obj):
144 # object handled itself, don't proceed
146 # object handled itself, don't proceed
145 return {}, {}
147 return {}, {}
146
148
147 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
149 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
148
150
149 if format_dict or md_dict:
151 if format_dict or md_dict:
150 if include:
152 if include:
151 format_dict = {k:v for k,v in format_dict.items() if k in include}
153 format_dict = {k:v for k,v in format_dict.items() if k in include}
152 md_dict = {k:v for k,v in md_dict.items() if k in include}
154 md_dict = {k:v for k,v in md_dict.items() if k in include}
153 if exclude:
155 if exclude:
154 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
156 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
155 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
157 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
156
158
157 for format_type, formatter in self.formatters.items():
159 for format_type, formatter in self.formatters.items():
158 if format_type in format_dict:
160 if format_type in format_dict:
159 # already got it from mimebundle, maybe don't render again.
161 # already got it from mimebundle, maybe don't render again.
160 # exception: manually registered per-mime renderer
162 # exception: manually registered per-mime renderer
161 # check priority:
163 # check priority:
162 # 1. user-registered per-mime formatter
164 # 1. user-registered per-mime formatter
163 # 2. mime-bundle (user-registered or repr method)
165 # 2. mime-bundle (user-registered or repr method)
164 # 3. default per-mime formatter (e.g. repr method)
166 # 3. default per-mime formatter (e.g. repr method)
165 try:
167 try:
166 formatter.lookup(obj)
168 formatter.lookup(obj)
167 except KeyError:
169 except KeyError:
168 # no special formatter, use mime-bundle-provided value
170 # no special formatter, use mime-bundle-provided value
169 continue
171 continue
170 if include and format_type not in include:
172 if include and format_type not in include:
171 continue
173 continue
172 if exclude and format_type in exclude:
174 if exclude and format_type in exclude:
173 continue
175 continue
174
176
175 md = None
177 md = None
176 try:
178 try:
177 data = formatter(obj)
179 data = formatter(obj)
178 except:
180 except:
179 # FIXME: log the exception
181 # FIXME: log the exception
180 raise
182 raise
181
183
182 # formatters can return raw data or (data, metadata)
184 # formatters can return raw data or (data, metadata)
183 if isinstance(data, tuple) and len(data) == 2:
185 if isinstance(data, tuple) and len(data) == 2:
184 data, md = data
186 data, md = data
185
187
186 if data is not None:
188 if data is not None:
187 format_dict[format_type] = data
189 format_dict[format_type] = data
188 if md is not None:
190 if md is not None:
189 md_dict[format_type] = md
191 md_dict[format_type] = md
190 return format_dict, md_dict
192 return format_dict, md_dict
191
193
192 @property
194 @property
193 def format_types(self):
195 def format_types(self):
194 """Return the format types (MIME types) of the active formatters."""
196 """Return the format types (MIME types) of the active formatters."""
195 return list(self.formatters.keys())
197 return list(self.formatters.keys())
196
198
197
199
198 #-----------------------------------------------------------------------------
200 #-----------------------------------------------------------------------------
199 # Formatters for specific format types (text, html, svg, etc.)
201 # Formatters for specific format types (text, html, svg, etc.)
200 #-----------------------------------------------------------------------------
202 #-----------------------------------------------------------------------------
201
203
202
204
203 def _safe_repr(obj):
205 def _safe_repr(obj):
204 """Try to return a repr of an object
206 """Try to return a repr of an object
205
207
206 always returns a string, at least.
208 always returns a string, at least.
207 """
209 """
208 try:
210 try:
209 return repr(obj)
211 return repr(obj)
210 except Exception as e:
212 except Exception as e:
211 return "un-repr-able object (%r)" % e
213 return "un-repr-able object (%r)" % e
212
214
213
215
214 class FormatterWarning(UserWarning):
216 class FormatterWarning(UserWarning):
215 """Warning class for errors in formatters"""
217 """Warning class for errors in formatters"""
216
218
217 @decorator
219 @decorator
218 def catch_format_error(method, self, *args, **kwargs):
220 def catch_format_error(method, self, *args, **kwargs):
219 """show traceback on failed format call"""
221 """show traceback on failed format call"""
220 try:
222 try:
221 r = method(self, *args, **kwargs)
223 r = method(self, *args, **kwargs)
222 except NotImplementedError:
224 except NotImplementedError:
223 # don't warn on NotImplementedErrors
225 # don't warn on NotImplementedErrors
224 return self._check_return(None, args[0])
226 return self._check_return(None, args[0])
225 except Exception:
227 except Exception:
226 exc_info = sys.exc_info()
228 exc_info = sys.exc_info()
227 ip = get_ipython()
229 ip = get_ipython()
228 if ip is not None:
230 if ip is not None:
229 ip.showtraceback(exc_info)
231 ip.showtraceback(exc_info)
230 else:
232 else:
231 traceback.print_exception(*exc_info)
233 traceback.print_exception(*exc_info)
232 return self._check_return(None, args[0])
234 return self._check_return(None, args[0])
233 return self._check_return(r, args[0])
235 return self._check_return(r, args[0])
234
236
235
237
236 class FormatterABC(metaclass=abc.ABCMeta):
238 class FormatterABC(metaclass=abc.ABCMeta):
237 """ Abstract base class for Formatters.
239 """ Abstract base class for Formatters.
238
240
239 A formatter is a callable class that is responsible for computing the
241 A formatter is a callable class that is responsible for computing the
240 raw format data for a particular format type (MIME type). For example,
242 raw format data for a particular format type (MIME type). For example,
241 an HTML formatter would have a format type of `text/html` and would return
243 an HTML formatter would have a format type of `text/html` and would return
242 the HTML representation of the object when called.
244 the HTML representation of the object when called.
243 """
245 """
244
246
245 # The format type of the data returned, usually a MIME type.
247 # The format type of the data returned, usually a MIME type.
246 format_type = 'text/plain'
248 format_type = 'text/plain'
247
249
248 # Is the formatter enabled...
250 # Is the formatter enabled...
249 enabled = True
251 enabled = True
250
252
251 @abc.abstractmethod
253 @abc.abstractmethod
252 def __call__(self, obj):
254 def __call__(self, obj):
253 """Return a JSON'able representation of the object.
255 """Return a JSON'able representation of the object.
254
256
255 If the object cannot be formatted by this formatter,
257 If the object cannot be formatted by this formatter,
256 warn and return None.
258 warn and return None.
257 """
259 """
258 return repr(obj)
260 return repr(obj)
259
261
260
262
261 def _mod_name_key(typ):
263 def _mod_name_key(typ):
262 """Return a (__module__, __name__) tuple for a type.
264 """Return a (__module__, __name__) tuple for a type.
263
265
264 Used as key in Formatter.deferred_printers.
266 Used as key in Formatter.deferred_printers.
265 """
267 """
266 module = getattr(typ, '__module__', None)
268 module = getattr(typ, '__module__', None)
267 name = getattr(typ, '__name__', None)
269 name = getattr(typ, '__name__', None)
268 return (module, name)
270 return (module, name)
269
271
270
272
271 def _get_type(obj):
273 def _get_type(obj):
272 """Return the type of an instance (old and new-style)"""
274 """Return the type of an instance (old and new-style)"""
273 return getattr(obj, '__class__', None) or type(obj)
275 return getattr(obj, '__class__', None) or type(obj)
274
276
275
277
276 _raise_key_error = Sentinel('_raise_key_error', __name__,
278 _raise_key_error = Sentinel('_raise_key_error', __name__,
277 """
279 """
278 Special value to raise a KeyError
280 Special value to raise a KeyError
279
281
280 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
282 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
281 """)
283 """)
282
284
283
285
284 class BaseFormatter(Configurable):
286 class BaseFormatter(Configurable):
285 """A base formatter class that is configurable.
287 """A base formatter class that is configurable.
286
288
287 This formatter should usually be used as the base class of all formatters.
289 This formatter should usually be used as the base class of all formatters.
288 It is a traited :class:`Configurable` class and includes an extensible
290 It is a traited :class:`Configurable` class and includes an extensible
289 API for users to determine how their objects are formatted. The following
291 API for users to determine how their objects are formatted. The following
290 logic is used to find a function to format an given object.
292 logic is used to find a function to format an given object.
291
293
292 1. The object is introspected to see if it has a method with the name
294 1. The object is introspected to see if it has a method with the name
293 :attr:`print_method`. If is does, that object is passed to that method
295 :attr:`print_method`. If is does, that object is passed to that method
294 for formatting.
296 for formatting.
295 2. If no print method is found, three internal dictionaries are consulted
297 2. If no print method is found, three internal dictionaries are consulted
296 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
298 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
297 and :attr:`deferred_printers`.
299 and :attr:`deferred_printers`.
298
300
299 Users should use these dictionaries to register functions that will be
301 Users should use these dictionaries to register functions that will be
300 used to compute the format data for their objects (if those objects don't
302 used to compute the format data for their objects (if those objects don't
301 have the special print methods). The easiest way of using these
303 have the special print methods). The easiest way of using these
302 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
304 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
303 methods.
305 methods.
304
306
305 If no function/callable is found to compute the format data, ``None`` is
307 If no function/callable is found to compute the format data, ``None`` is
306 returned and this format type is not used.
308 returned and this format type is not used.
307 """
309 """
308
310
309 format_type = Unicode('text/plain')
311 format_type = Unicode("text/plain")
310 _return_type = str
312 _return_type: Any = str
311
313
312 enabled = Bool(True).tag(config=True)
314 enabled = Bool(True).tag(config=True)
313
315
314 print_method = ObjectName('__repr__')
316 print_method = ObjectName('__repr__')
315
317
316 # The singleton printers.
318 # The singleton printers.
317 # Maps the IDs of the builtin singleton objects to the format functions.
319 # Maps the IDs of the builtin singleton objects to the format functions.
318 singleton_printers = Dict().tag(config=True)
320 singleton_printers = Dict().tag(config=True)
319
321
320 # The type-specific printers.
322 # The type-specific printers.
321 # Map type objects to the format functions.
323 # Map type objects to the format functions.
322 type_printers = Dict().tag(config=True)
324 type_printers = Dict().tag(config=True)
323
325
324 # The deferred-import type-specific printers.
326 # The deferred-import type-specific printers.
325 # Map (modulename, classname) pairs to the format functions.
327 # Map (modulename, classname) pairs to the format functions.
326 deferred_printers = Dict().tag(config=True)
328 deferred_printers = Dict().tag(config=True)
327
329
328 @catch_format_error
330 @catch_format_error
329 def __call__(self, obj):
331 def __call__(self, obj):
330 """Compute the format for an object."""
332 """Compute the format for an object."""
331 if self.enabled:
333 if self.enabled:
332 # lookup registered printer
334 # lookup registered printer
333 try:
335 try:
334 printer = self.lookup(obj)
336 printer = self.lookup(obj)
335 except KeyError:
337 except KeyError:
336 pass
338 pass
337 else:
339 else:
338 return printer(obj)
340 return printer(obj)
339 # Finally look for special method names
341 # Finally look for special method names
340 method = get_real_method(obj, self.print_method)
342 method = get_real_method(obj, self.print_method)
341 if method is not None:
343 if method is not None:
342 return method()
344 return method()
343 return None
345 return None
344 else:
346 else:
345 return None
347 return None
346
348
347 def __contains__(self, typ):
349 def __contains__(self, typ):
348 """map in to lookup_by_type"""
350 """map in to lookup_by_type"""
349 try:
351 try:
350 self.lookup_by_type(typ)
352 self.lookup_by_type(typ)
351 except KeyError:
353 except KeyError:
352 return False
354 return False
353 else:
355 else:
354 return True
356 return True
355
357
356 def _check_return(self, r, obj):
358 def _check_return(self, r, obj):
357 """Check that a return value is appropriate
359 """Check that a return value is appropriate
358
360
359 Return the value if so, None otherwise, warning if invalid.
361 Return the value if so, None otherwise, warning if invalid.
360 """
362 """
361 if r is None or isinstance(r, self._return_type) or \
363 if r is None or isinstance(r, self._return_type) or \
362 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
364 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
363 return r
365 return r
364 else:
366 else:
365 warnings.warn(
367 warnings.warn(
366 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
368 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
367 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
369 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
368 FormatterWarning
370 FormatterWarning
369 )
371 )
370
372
371 def lookup(self, obj):
373 def lookup(self, obj):
372 """Look up the formatter for a given instance.
374 """Look up the formatter for a given instance.
373
375
374 Parameters
376 Parameters
375 ----------
377 ----------
376 obj : object instance
378 obj : object instance
377
379
378 Returns
380 Returns
379 -------
381 -------
380 f : callable
382 f : callable
381 The registered formatting callable for the type.
383 The registered formatting callable for the type.
382
384
383 Raises
385 Raises
384 ------
386 ------
385 KeyError if the type has not been registered.
387 KeyError if the type has not been registered.
386 """
388 """
387 # look for singleton first
389 # look for singleton first
388 obj_id = id(obj)
390 obj_id = id(obj)
389 if obj_id in self.singleton_printers:
391 if obj_id in self.singleton_printers:
390 return self.singleton_printers[obj_id]
392 return self.singleton_printers[obj_id]
391 # then lookup by type
393 # then lookup by type
392 return self.lookup_by_type(_get_type(obj))
394 return self.lookup_by_type(_get_type(obj))
393
395
394 def lookup_by_type(self, typ):
396 def lookup_by_type(self, typ):
395 """Look up the registered formatter for a type.
397 """Look up the registered formatter for a type.
396
398
397 Parameters
399 Parameters
398 ----------
400 ----------
399 typ : type or '__module__.__name__' string for a type
401 typ : type or '__module__.__name__' string for a type
400
402
401 Returns
403 Returns
402 -------
404 -------
403 f : callable
405 f : callable
404 The registered formatting callable for the type.
406 The registered formatting callable for the type.
405
407
406 Raises
408 Raises
407 ------
409 ------
408 KeyError if the type has not been registered.
410 KeyError if the type has not been registered.
409 """
411 """
410 if isinstance(typ, str):
412 if isinstance(typ, str):
411 typ_key = tuple(typ.rsplit('.',1))
413 typ_key = tuple(typ.rsplit('.',1))
412 if typ_key not in self.deferred_printers:
414 if typ_key not in self.deferred_printers:
413 # We may have it cached in the type map. We will have to
415 # We may have it cached in the type map. We will have to
414 # iterate over all of the types to check.
416 # iterate over all of the types to check.
415 for cls in self.type_printers:
417 for cls in self.type_printers:
416 if _mod_name_key(cls) == typ_key:
418 if _mod_name_key(cls) == typ_key:
417 return self.type_printers[cls]
419 return self.type_printers[cls]
418 else:
420 else:
419 return self.deferred_printers[typ_key]
421 return self.deferred_printers[typ_key]
420 else:
422 else:
421 for cls in pretty._get_mro(typ):
423 for cls in pretty._get_mro(typ):
422 if cls in self.type_printers or self._in_deferred_types(cls):
424 if cls in self.type_printers or self._in_deferred_types(cls):
423 return self.type_printers[cls]
425 return self.type_printers[cls]
424
426
425 # If we have reached here, the lookup failed.
427 # If we have reached here, the lookup failed.
426 raise KeyError("No registered printer for {0!r}".format(typ))
428 raise KeyError("No registered printer for {0!r}".format(typ))
427
429
428 def for_type(self, typ, func=None):
430 def for_type(self, typ, func=None):
429 """Add a format function for a given type.
431 """Add a format function for a given type.
430
432
431 Parameters
433 Parameters
432 ----------
434 ----------
433 typ : type or '__module__.__name__' string for a type
435 typ : type or '__module__.__name__' string for a type
434 The class of the object that will be formatted using `func`.
436 The class of the object that will be formatted using `func`.
435
437
436 func : callable
438 func : callable
437 A callable for computing the format data.
439 A callable for computing the format data.
438 `func` will be called with the object to be formatted,
440 `func` will be called with the object to be formatted,
439 and will return the raw data in this formatter's format.
441 and will return the raw data in this formatter's format.
440 Subclasses may use a different call signature for the
442 Subclasses may use a different call signature for the
441 `func` argument.
443 `func` argument.
442
444
443 If `func` is None or not specified, there will be no change,
445 If `func` is None or not specified, there will be no change,
444 only returning the current value.
446 only returning the current value.
445
447
446 Returns
448 Returns
447 -------
449 -------
448 oldfunc : callable
450 oldfunc : callable
449 The currently registered callable.
451 The currently registered callable.
450 If you are registering a new formatter,
452 If you are registering a new formatter,
451 this will be the previous value (to enable restoring later).
453 this will be the previous value (to enable restoring later).
452 """
454 """
453 # if string given, interpret as 'pkg.module.class_name'
455 # if string given, interpret as 'pkg.module.class_name'
454 if isinstance(typ, str):
456 if isinstance(typ, str):
455 type_module, type_name = typ.rsplit('.', 1)
457 type_module, type_name = typ.rsplit('.', 1)
456 return self.for_type_by_name(type_module, type_name, func)
458 return self.for_type_by_name(type_module, type_name, func)
457
459
458 try:
460 try:
459 oldfunc = self.lookup_by_type(typ)
461 oldfunc = self.lookup_by_type(typ)
460 except KeyError:
462 except KeyError:
461 oldfunc = None
463 oldfunc = None
462
464
463 if func is not None:
465 if func is not None:
464 self.type_printers[typ] = func
466 self.type_printers[typ] = func
465
467
466 return oldfunc
468 return oldfunc
467
469
468 def for_type_by_name(self, type_module, type_name, func=None):
470 def for_type_by_name(self, type_module, type_name, func=None):
469 """Add a format function for a type specified by the full dotted
471 """Add a format function for a type specified by the full dotted
470 module and name of the type, rather than the type of the object.
472 module and name of the type, rather than the type of the object.
471
473
472 Parameters
474 Parameters
473 ----------
475 ----------
474 type_module : str
476 type_module : str
475 The full dotted name of the module the type is defined in, like
477 The full dotted name of the module the type is defined in, like
476 ``numpy``.
478 ``numpy``.
477
479
478 type_name : str
480 type_name : str
479 The name of the type (the class name), like ``dtype``
481 The name of the type (the class name), like ``dtype``
480
482
481 func : callable
483 func : callable
482 A callable for computing the format data.
484 A callable for computing the format data.
483 `func` will be called with the object to be formatted,
485 `func` will be called with the object to be formatted,
484 and will return the raw data in this formatter's format.
486 and will return the raw data in this formatter's format.
485 Subclasses may use a different call signature for the
487 Subclasses may use a different call signature for the
486 `func` argument.
488 `func` argument.
487
489
488 If `func` is None or unspecified, there will be no change,
490 If `func` is None or unspecified, there will be no change,
489 only returning the current value.
491 only returning the current value.
490
492
491 Returns
493 Returns
492 -------
494 -------
493 oldfunc : callable
495 oldfunc : callable
494 The currently registered callable.
496 The currently registered callable.
495 If you are registering a new formatter,
497 If you are registering a new formatter,
496 this will be the previous value (to enable restoring later).
498 this will be the previous value (to enable restoring later).
497 """
499 """
498 key = (type_module, type_name)
500 key = (type_module, type_name)
499
501
500 try:
502 try:
501 oldfunc = self.lookup_by_type("%s.%s" % key)
503 oldfunc = self.lookup_by_type("%s.%s" % key)
502 except KeyError:
504 except KeyError:
503 oldfunc = None
505 oldfunc = None
504
506
505 if func is not None:
507 if func is not None:
506 self.deferred_printers[key] = func
508 self.deferred_printers[key] = func
507 return oldfunc
509 return oldfunc
508
510
509 def pop(self, typ, default=_raise_key_error):
511 def pop(self, typ, default=_raise_key_error):
510 """Pop a formatter for the given type.
512 """Pop a formatter for the given type.
511
513
512 Parameters
514 Parameters
513 ----------
515 ----------
514 typ : type or '__module__.__name__' string for a type
516 typ : type or '__module__.__name__' string for a type
515 default : object
517 default : object
516 value to be returned if no formatter is registered for typ.
518 value to be returned if no formatter is registered for typ.
517
519
518 Returns
520 Returns
519 -------
521 -------
520 obj : object
522 obj : object
521 The last registered object for the type.
523 The last registered object for the type.
522
524
523 Raises
525 Raises
524 ------
526 ------
525 KeyError if the type is not registered and default is not specified.
527 KeyError if the type is not registered and default is not specified.
526 """
528 """
527
529
528 if isinstance(typ, str):
530 if isinstance(typ, str):
529 typ_key = tuple(typ.rsplit('.',1))
531 typ_key = tuple(typ.rsplit('.',1))
530 if typ_key not in self.deferred_printers:
532 if typ_key not in self.deferred_printers:
531 # We may have it cached in the type map. We will have to
533 # We may have it cached in the type map. We will have to
532 # iterate over all of the types to check.
534 # iterate over all of the types to check.
533 for cls in self.type_printers:
535 for cls in self.type_printers:
534 if _mod_name_key(cls) == typ_key:
536 if _mod_name_key(cls) == typ_key:
535 old = self.type_printers.pop(cls)
537 old = self.type_printers.pop(cls)
536 break
538 break
537 else:
539 else:
538 old = default
540 old = default
539 else:
541 else:
540 old = self.deferred_printers.pop(typ_key)
542 old = self.deferred_printers.pop(typ_key)
541 else:
543 else:
542 if typ in self.type_printers:
544 if typ in self.type_printers:
543 old = self.type_printers.pop(typ)
545 old = self.type_printers.pop(typ)
544 else:
546 else:
545 old = self.deferred_printers.pop(_mod_name_key(typ), default)
547 old = self.deferred_printers.pop(_mod_name_key(typ), default)
546 if old is _raise_key_error:
548 if old is _raise_key_error:
547 raise KeyError("No registered value for {0!r}".format(typ))
549 raise KeyError("No registered value for {0!r}".format(typ))
548 return old
550 return old
549
551
550 def _in_deferred_types(self, cls):
552 def _in_deferred_types(self, cls):
551 """
553 """
552 Check if the given class is specified in the deferred type registry.
554 Check if the given class is specified in the deferred type registry.
553
555
554 Successful matches will be moved to the regular type registry for future use.
556 Successful matches will be moved to the regular type registry for future use.
555 """
557 """
556 mod = getattr(cls, '__module__', None)
558 mod = getattr(cls, '__module__', None)
557 name = getattr(cls, '__name__', None)
559 name = getattr(cls, '__name__', None)
558 key = (mod, name)
560 key = (mod, name)
559 if key in self.deferred_printers:
561 if key in self.deferred_printers:
560 # Move the printer over to the regular registry.
562 # Move the printer over to the regular registry.
561 printer = self.deferred_printers.pop(key)
563 printer = self.deferred_printers.pop(key)
562 self.type_printers[cls] = printer
564 self.type_printers[cls] = printer
563 return True
565 return True
564 return False
566 return False
565
567
566
568
567 class PlainTextFormatter(BaseFormatter):
569 class PlainTextFormatter(BaseFormatter):
568 """The default pretty-printer.
570 """The default pretty-printer.
569
571
570 This uses :mod:`IPython.lib.pretty` to compute the format data of
572 This uses :mod:`IPython.lib.pretty` to compute the format data of
571 the object. If the object cannot be pretty printed, :func:`repr` is used.
573 the object. If the object cannot be pretty printed, :func:`repr` is used.
572 See the documentation of :mod:`IPython.lib.pretty` for details on
574 See the documentation of :mod:`IPython.lib.pretty` for details on
573 how to write pretty printers. Here is a simple example::
575 how to write pretty printers. Here is a simple example::
574
576
575 def dtype_pprinter(obj, p, cycle):
577 def dtype_pprinter(obj, p, cycle):
576 if cycle:
578 if cycle:
577 return p.text('dtype(...)')
579 return p.text('dtype(...)')
578 if hasattr(obj, 'fields'):
580 if hasattr(obj, 'fields'):
579 if obj.fields is None:
581 if obj.fields is None:
580 p.text(repr(obj))
582 p.text(repr(obj))
581 else:
583 else:
582 p.begin_group(7, 'dtype([')
584 p.begin_group(7, 'dtype([')
583 for i, field in enumerate(obj.descr):
585 for i, field in enumerate(obj.descr):
584 if i > 0:
586 if i > 0:
585 p.text(',')
587 p.text(',')
586 p.breakable()
588 p.breakable()
587 p.pretty(field)
589 p.pretty(field)
588 p.end_group(7, '])')
590 p.end_group(7, '])')
589 """
591 """
590
592
591 # The format type of data returned.
593 # The format type of data returned.
592 format_type = Unicode('text/plain')
594 format_type = Unicode('text/plain')
593
595
594 # This subclass ignores this attribute as it always need to return
596 # This subclass ignores this attribute as it always need to return
595 # something.
597 # something.
596 enabled = Bool(True).tag(config=False)
598 enabled = Bool(True).tag(config=False)
597
599
598 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
600 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
599 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
601 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
600
602
601 Set to 0 to disable truncation.
603 Set to 0 to disable truncation.
602 """
604 """
603 ).tag(config=True)
605 ).tag(config=True)
604
606
605 # Look for a _repr_pretty_ methods to use for pretty printing.
607 # Look for a _repr_pretty_ methods to use for pretty printing.
606 print_method = ObjectName('_repr_pretty_')
608 print_method = ObjectName('_repr_pretty_')
607
609
608 # Whether to pretty-print or not.
610 # Whether to pretty-print or not.
609 pprint = Bool(True).tag(config=True)
611 pprint = Bool(True).tag(config=True)
610
612
611 # Whether to be verbose or not.
613 # Whether to be verbose or not.
612 verbose = Bool(False).tag(config=True)
614 verbose = Bool(False).tag(config=True)
613
615
614 # The maximum width.
616 # The maximum width.
615 max_width = Integer(79).tag(config=True)
617 max_width = Integer(79).tag(config=True)
616
618
617 # The newline character.
619 # The newline character.
618 newline = Unicode('\n').tag(config=True)
620 newline = Unicode('\n').tag(config=True)
619
621
620 # format-string for pprinting floats
622 # format-string for pprinting floats
621 float_format = Unicode('%r')
623 float_format = Unicode('%r')
622 # setter for float precision, either int or direct format-string
624 # setter for float precision, either int or direct format-string
623 float_precision = CUnicode('').tag(config=True)
625 float_precision = CUnicode('').tag(config=True)
624
626
625 @observe('float_precision')
627 @observe('float_precision')
626 def _float_precision_changed(self, change):
628 def _float_precision_changed(self, change):
627 """float_precision changed, set float_format accordingly.
629 """float_precision changed, set float_format accordingly.
628
630
629 float_precision can be set by int or str.
631 float_precision can be set by int or str.
630 This will set float_format, after interpreting input.
632 This will set float_format, after interpreting input.
631 If numpy has been imported, numpy print precision will also be set.
633 If numpy has been imported, numpy print precision will also be set.
632
634
633 integer `n` sets format to '%.nf', otherwise, format set directly.
635 integer `n` sets format to '%.nf', otherwise, format set directly.
634
636
635 An empty string returns to defaults (repr for float, 8 for numpy).
637 An empty string returns to defaults (repr for float, 8 for numpy).
636
638
637 This parameter can be set via the '%precision' magic.
639 This parameter can be set via the '%precision' magic.
638 """
640 """
639 new = change['new']
641 new = change['new']
640 if '%' in new:
642 if '%' in new:
641 # got explicit format string
643 # got explicit format string
642 fmt = new
644 fmt = new
643 try:
645 try:
644 fmt%3.14159
646 fmt%3.14159
645 except Exception as e:
647 except Exception as e:
646 raise ValueError("Precision must be int or format string, not %r"%new) from e
648 raise ValueError("Precision must be int or format string, not %r"%new) from e
647 elif new:
649 elif new:
648 # otherwise, should be an int
650 # otherwise, should be an int
649 try:
651 try:
650 i = int(new)
652 i = int(new)
651 assert i >= 0
653 assert i >= 0
652 except ValueError as e:
654 except ValueError as e:
653 raise ValueError("Precision must be int or format string, not %r"%new) from e
655 raise ValueError("Precision must be int or format string, not %r"%new) from e
654 except AssertionError as e:
656 except AssertionError as e:
655 raise ValueError("int precision must be non-negative, not %r"%i) from e
657 raise ValueError("int precision must be non-negative, not %r"%i) from e
656
658
657 fmt = '%%.%if'%i
659 fmt = '%%.%if'%i
658 if 'numpy' in sys.modules:
660 if 'numpy' in sys.modules:
659 # set numpy precision if it has been imported
661 # set numpy precision if it has been imported
660 import numpy
662 import numpy
661 numpy.set_printoptions(precision=i)
663 numpy.set_printoptions(precision=i)
662 else:
664 else:
663 # default back to repr
665 # default back to repr
664 fmt = '%r'
666 fmt = '%r'
665 if 'numpy' in sys.modules:
667 if 'numpy' in sys.modules:
666 import numpy
668 import numpy
667 # numpy default is 8
669 # numpy default is 8
668 numpy.set_printoptions(precision=8)
670 numpy.set_printoptions(precision=8)
669 self.float_format = fmt
671 self.float_format = fmt
670
672
671 # Use the default pretty printers from IPython.lib.pretty.
673 # Use the default pretty printers from IPython.lib.pretty.
672 @default('singleton_printers')
674 @default('singleton_printers')
673 def _singleton_printers_default(self):
675 def _singleton_printers_default(self):
674 return pretty._singleton_pprinters.copy()
676 return pretty._singleton_pprinters.copy()
675
677
676 @default('type_printers')
678 @default('type_printers')
677 def _type_printers_default(self):
679 def _type_printers_default(self):
678 d = pretty._type_pprinters.copy()
680 d = pretty._type_pprinters.copy()
679 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
681 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
680 # if NumPy is used, set precision for its float64 type
682 # if NumPy is used, set precision for its float64 type
681 if "numpy" in sys.modules:
683 if "numpy" in sys.modules:
682 import numpy
684 import numpy
683
685
684 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
686 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
685 return d
687 return d
686
688
687 @default('deferred_printers')
689 @default('deferred_printers')
688 def _deferred_printers_default(self):
690 def _deferred_printers_default(self):
689 return pretty._deferred_type_pprinters.copy()
691 return pretty._deferred_type_pprinters.copy()
690
692
691 #### FormatterABC interface ####
693 #### FormatterABC interface ####
692
694
693 @catch_format_error
695 @catch_format_error
694 def __call__(self, obj):
696 def __call__(self, obj):
695 """Compute the pretty representation of the object."""
697 """Compute the pretty representation of the object."""
696 if not self.pprint:
698 if not self.pprint:
697 return repr(obj)
699 return repr(obj)
698 else:
700 else:
699 stream = StringIO()
701 stream = StringIO()
700 printer = pretty.RepresentationPrinter(stream, self.verbose,
702 printer = pretty.RepresentationPrinter(stream, self.verbose,
701 self.max_width, self.newline,
703 self.max_width, self.newline,
702 max_seq_length=self.max_seq_length,
704 max_seq_length=self.max_seq_length,
703 singleton_pprinters=self.singleton_printers,
705 singleton_pprinters=self.singleton_printers,
704 type_pprinters=self.type_printers,
706 type_pprinters=self.type_printers,
705 deferred_pprinters=self.deferred_printers)
707 deferred_pprinters=self.deferred_printers)
706 printer.pretty(obj)
708 printer.pretty(obj)
707 printer.flush()
709 printer.flush()
708 return stream.getvalue()
710 return stream.getvalue()
709
711
710
712
711 class HTMLFormatter(BaseFormatter):
713 class HTMLFormatter(BaseFormatter):
712 """An HTML formatter.
714 """An HTML formatter.
713
715
714 To define the callables that compute the HTML representation of your
716 To define the callables that compute the HTML representation of your
715 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
717 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
716 or :meth:`for_type_by_name` methods to register functions that handle
718 or :meth:`for_type_by_name` methods to register functions that handle
717 this.
719 this.
718
720
719 The return value of this formatter should be a valid HTML snippet that
721 The return value of this formatter should be a valid HTML snippet that
720 could be injected into an existing DOM. It should *not* include the
722 could be injected into an existing DOM. It should *not* include the
721 ```<html>`` or ```<body>`` tags.
723 ```<html>`` or ```<body>`` tags.
722 """
724 """
723 format_type = Unicode('text/html')
725 format_type = Unicode('text/html')
724
726
725 print_method = ObjectName('_repr_html_')
727 print_method = ObjectName('_repr_html_')
726
728
727
729
728 class MarkdownFormatter(BaseFormatter):
730 class MarkdownFormatter(BaseFormatter):
729 """A Markdown formatter.
731 """A Markdown formatter.
730
732
731 To define the callables that compute the Markdown representation of your
733 To define the callables that compute the Markdown representation of your
732 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
734 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
733 or :meth:`for_type_by_name` methods to register functions that handle
735 or :meth:`for_type_by_name` methods to register functions that handle
734 this.
736 this.
735
737
736 The return value of this formatter should be a valid Markdown.
738 The return value of this formatter should be a valid Markdown.
737 """
739 """
738 format_type = Unicode('text/markdown')
740 format_type = Unicode('text/markdown')
739
741
740 print_method = ObjectName('_repr_markdown_')
742 print_method = ObjectName('_repr_markdown_')
741
743
742 class SVGFormatter(BaseFormatter):
744 class SVGFormatter(BaseFormatter):
743 """An SVG formatter.
745 """An SVG formatter.
744
746
745 To define the callables that compute the SVG representation of your
747 To define the callables that compute the SVG representation of your
746 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
748 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
747 or :meth:`for_type_by_name` methods to register functions that handle
749 or :meth:`for_type_by_name` methods to register functions that handle
748 this.
750 this.
749
751
750 The return value of this formatter should be valid SVG enclosed in
752 The return value of this formatter should be valid SVG enclosed in
751 ```<svg>``` tags, that could be injected into an existing DOM. It should
753 ```<svg>``` tags, that could be injected into an existing DOM. It should
752 *not* include the ```<html>`` or ```<body>`` tags.
754 *not* include the ```<html>`` or ```<body>`` tags.
753 """
755 """
754 format_type = Unicode('image/svg+xml')
756 format_type = Unicode('image/svg+xml')
755
757
756 print_method = ObjectName('_repr_svg_')
758 print_method = ObjectName('_repr_svg_')
757
759
758
760
759 class PNGFormatter(BaseFormatter):
761 class PNGFormatter(BaseFormatter):
760 """A PNG formatter.
762 """A PNG formatter.
761
763
762 To define the callables that compute the PNG representation of your
764 To define the callables that compute the PNG representation of your
763 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
765 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
764 or :meth:`for_type_by_name` methods to register functions that handle
766 or :meth:`for_type_by_name` methods to register functions that handle
765 this.
767 this.
766
768
767 The return value of this formatter should be raw PNG data, *not*
769 The return value of this formatter should be raw PNG data, *not*
768 base64 encoded.
770 base64 encoded.
769 """
771 """
770 format_type = Unicode('image/png')
772 format_type = Unicode('image/png')
771
773
772 print_method = ObjectName('_repr_png_')
774 print_method = ObjectName('_repr_png_')
773
775
774 _return_type = (bytes, str)
776 _return_type = (bytes, str)
775
777
776
778
777 class JPEGFormatter(BaseFormatter):
779 class JPEGFormatter(BaseFormatter):
778 """A JPEG formatter.
780 """A JPEG formatter.
779
781
780 To define the callables that compute the JPEG representation of your
782 To define the callables that compute the JPEG representation of your
781 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
783 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
782 or :meth:`for_type_by_name` methods to register functions that handle
784 or :meth:`for_type_by_name` methods to register functions that handle
783 this.
785 this.
784
786
785 The return value of this formatter should be raw JPEG data, *not*
787 The return value of this formatter should be raw JPEG data, *not*
786 base64 encoded.
788 base64 encoded.
787 """
789 """
788 format_type = Unicode('image/jpeg')
790 format_type = Unicode('image/jpeg')
789
791
790 print_method = ObjectName('_repr_jpeg_')
792 print_method = ObjectName('_repr_jpeg_')
791
793
792 _return_type = (bytes, str)
794 _return_type = (bytes, str)
793
795
794
796
795 class LatexFormatter(BaseFormatter):
797 class LatexFormatter(BaseFormatter):
796 """A LaTeX formatter.
798 """A LaTeX formatter.
797
799
798 To define the callables that compute the LaTeX representation of your
800 To define the callables that compute the LaTeX representation of your
799 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
801 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
800 or :meth:`for_type_by_name` methods to register functions that handle
802 or :meth:`for_type_by_name` methods to register functions that handle
801 this.
803 this.
802
804
803 The return value of this formatter should be a valid LaTeX equation,
805 The return value of this formatter should be a valid LaTeX equation,
804 enclosed in either ```$```, ```$$``` or another LaTeX equation
806 enclosed in either ```$```, ```$$``` or another LaTeX equation
805 environment.
807 environment.
806 """
808 """
807 format_type = Unicode('text/latex')
809 format_type = Unicode('text/latex')
808
810
809 print_method = ObjectName('_repr_latex_')
811 print_method = ObjectName('_repr_latex_')
810
812
811
813
812 class JSONFormatter(BaseFormatter):
814 class JSONFormatter(BaseFormatter):
813 """A JSON string formatter.
815 """A JSON string formatter.
814
816
815 To define the callables that compute the JSONable representation of
817 To define the callables that compute the JSONable representation of
816 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
818 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
817 or :meth:`for_type_by_name` methods to register functions that handle
819 or :meth:`for_type_by_name` methods to register functions that handle
818 this.
820 this.
819
821
820 The return value of this formatter should be a JSONable list or dict.
822 The return value of this formatter should be a JSONable list or dict.
821 JSON scalars (None, number, string) are not allowed, only dict or list containers.
823 JSON scalars (None, number, string) are not allowed, only dict or list containers.
822 """
824 """
823 format_type = Unicode('application/json')
825 format_type = Unicode('application/json')
824 _return_type = (list, dict)
826 _return_type = (list, dict)
825
827
826 print_method = ObjectName('_repr_json_')
828 print_method = ObjectName('_repr_json_')
827
829
828 def _check_return(self, r, obj):
830 def _check_return(self, r, obj):
829 """Check that a return value is appropriate
831 """Check that a return value is appropriate
830
832
831 Return the value if so, None otherwise, warning if invalid.
833 Return the value if so, None otherwise, warning if invalid.
832 """
834 """
833 if r is None:
835 if r is None:
834 return
836 return
835 md = None
837 md = None
836 if isinstance(r, tuple):
838 if isinstance(r, tuple):
837 # unpack data, metadata tuple for type checking on first element
839 # unpack data, metadata tuple for type checking on first element
838 r, md = r
840 r, md = r
839
841
840 assert not isinstance(
842 assert not isinstance(
841 r, str
843 r, str
842 ), "JSON-as-string has been deprecated since IPython < 3"
844 ), "JSON-as-string has been deprecated since IPython < 3"
843
845
844 if md is not None:
846 if md is not None:
845 # put the tuple back together
847 # put the tuple back together
846 r = (r, md)
848 r = (r, md)
847 return super(JSONFormatter, self)._check_return(r, obj)
849 return super(JSONFormatter, self)._check_return(r, obj)
848
850
849
851
850 class JavascriptFormatter(BaseFormatter):
852 class JavascriptFormatter(BaseFormatter):
851 """A Javascript formatter.
853 """A Javascript formatter.
852
854
853 To define the callables that compute the Javascript representation of
855 To define the callables that compute the Javascript representation of
854 your objects, define a :meth:`_repr_javascript_` method or use the
856 your objects, define a :meth:`_repr_javascript_` method or use the
855 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
857 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
856 that handle this.
858 that handle this.
857
859
858 The return value of this formatter should be valid Javascript code and
860 The return value of this formatter should be valid Javascript code and
859 should *not* be enclosed in ```<script>``` tags.
861 should *not* be enclosed in ```<script>``` tags.
860 """
862 """
861 format_type = Unicode('application/javascript')
863 format_type = Unicode('application/javascript')
862
864
863 print_method = ObjectName('_repr_javascript_')
865 print_method = ObjectName('_repr_javascript_')
864
866
865
867
866 class PDFFormatter(BaseFormatter):
868 class PDFFormatter(BaseFormatter):
867 """A PDF formatter.
869 """A PDF formatter.
868
870
869 To define the callables that compute the PDF representation of your
871 To define the callables that compute the PDF representation of your
870 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
872 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
871 or :meth:`for_type_by_name` methods to register functions that handle
873 or :meth:`for_type_by_name` methods to register functions that handle
872 this.
874 this.
873
875
874 The return value of this formatter should be raw PDF data, *not*
876 The return value of this formatter should be raw PDF data, *not*
875 base64 encoded.
877 base64 encoded.
876 """
878 """
877 format_type = Unicode('application/pdf')
879 format_type = Unicode('application/pdf')
878
880
879 print_method = ObjectName('_repr_pdf_')
881 print_method = ObjectName('_repr_pdf_')
880
882
881 _return_type = (bytes, str)
883 _return_type = (bytes, str)
882
884
883 class IPythonDisplayFormatter(BaseFormatter):
885 class IPythonDisplayFormatter(BaseFormatter):
884 """An escape-hatch Formatter for objects that know how to display themselves.
886 """An escape-hatch Formatter for objects that know how to display themselves.
885
887
886 To define the callables that compute the representation of your
888 To define the callables that compute the representation of your
887 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
889 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
888 or :meth:`for_type_by_name` methods to register functions that handle
890 or :meth:`for_type_by_name` methods to register functions that handle
889 this. Unlike mime-type displays, this method should not return anything,
891 this. Unlike mime-type displays, this method should not return anything,
890 instead calling any appropriate display methods itself.
892 instead calling any appropriate display methods itself.
891
893
892 This display formatter has highest priority.
894 This display formatter has highest priority.
893 If it fires, no other display formatter will be called.
895 If it fires, no other display formatter will be called.
894
896
895 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
897 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
896 without registering a new Formatter.
898 without registering a new Formatter.
897
899
898 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
900 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
899 so `_ipython_display_` should only be used for objects that require unusual
901 so `_ipython_display_` should only be used for objects that require unusual
900 display patterns, such as multiple display calls.
902 display patterns, such as multiple display calls.
901 """
903 """
902 print_method = ObjectName('_ipython_display_')
904 print_method = ObjectName('_ipython_display_')
903 _return_type = (type(None), bool)
905 _return_type = (type(None), bool)
904
906
905 @catch_format_error
907 @catch_format_error
906 def __call__(self, obj):
908 def __call__(self, obj):
907 """Compute the format for an object."""
909 """Compute the format for an object."""
908 if self.enabled:
910 if self.enabled:
909 # lookup registered printer
911 # lookup registered printer
910 try:
912 try:
911 printer = self.lookup(obj)
913 printer = self.lookup(obj)
912 except KeyError:
914 except KeyError:
913 pass
915 pass
914 else:
916 else:
915 printer(obj)
917 printer(obj)
916 return True
918 return True
917 # Finally look for special method names
919 # Finally look for special method names
918 method = get_real_method(obj, self.print_method)
920 method = get_real_method(obj, self.print_method)
919 if method is not None:
921 if method is not None:
920 method()
922 method()
921 return True
923 return True
922
924
923
925
924 class MimeBundleFormatter(BaseFormatter):
926 class MimeBundleFormatter(BaseFormatter):
925 """A Formatter for arbitrary mime-types.
927 """A Formatter for arbitrary mime-types.
926
928
927 Unlike other `_repr_<mimetype>_` methods,
929 Unlike other `_repr_<mimetype>_` methods,
928 `_repr_mimebundle_` should return mime-bundle data,
930 `_repr_mimebundle_` should return mime-bundle data,
929 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
931 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
930 Any mime-type is valid.
932 Any mime-type is valid.
931
933
932 To define the callables that compute the mime-bundle representation of your
934 To define the callables that compute the mime-bundle representation of your
933 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
935 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
934 or :meth:`for_type_by_name` methods to register functions that handle
936 or :meth:`for_type_by_name` methods to register functions that handle
935 this.
937 this.
936
938
937 .. versionadded:: 6.1
939 .. versionadded:: 6.1
938 """
940 """
939 print_method = ObjectName('_repr_mimebundle_')
941 print_method = ObjectName('_repr_mimebundle_')
940 _return_type = dict
942 _return_type = dict
941
943
942 def _check_return(self, r, obj):
944 def _check_return(self, r, obj):
943 r = super(MimeBundleFormatter, self)._check_return(r, obj)
945 r = super(MimeBundleFormatter, self)._check_return(r, obj)
944 # always return (data, metadata):
946 # always return (data, metadata):
945 if r is None:
947 if r is None:
946 return {}, {}
948 return {}, {}
947 if not isinstance(r, tuple):
949 if not isinstance(r, tuple):
948 return r, {}
950 return r, {}
949 return r
951 return r
950
952
951 @catch_format_error
953 @catch_format_error
952 def __call__(self, obj, include=None, exclude=None):
954 def __call__(self, obj, include=None, exclude=None):
953 """Compute the format for an object.
955 """Compute the format for an object.
954
956
955 Identical to parent's method but we pass extra parameters to the method.
957 Identical to parent's method but we pass extra parameters to the method.
956
958
957 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
959 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
958 particular `include` and `exclude`.
960 particular `include` and `exclude`.
959 """
961 """
960 if self.enabled:
962 if self.enabled:
961 # lookup registered printer
963 # lookup registered printer
962 try:
964 try:
963 printer = self.lookup(obj)
965 printer = self.lookup(obj)
964 except KeyError:
966 except KeyError:
965 pass
967 pass
966 else:
968 else:
967 return printer(obj)
969 return printer(obj)
968 # Finally look for special method names
970 # Finally look for special method names
969 method = get_real_method(obj, self.print_method)
971 method = get_real_method(obj, self.print_method)
970
972
971 if method is not None:
973 if method is not None:
972 return method(include=include, exclude=exclude)
974 return method(include=include, exclude=exclude)
973 return None
975 return None
974 else:
976 else:
975 return None
977 return None
976
978
977
979
978 FormatterABC.register(BaseFormatter)
980 FormatterABC.register(BaseFormatter)
979 FormatterABC.register(PlainTextFormatter)
981 FormatterABC.register(PlainTextFormatter)
980 FormatterABC.register(HTMLFormatter)
982 FormatterABC.register(HTMLFormatter)
981 FormatterABC.register(MarkdownFormatter)
983 FormatterABC.register(MarkdownFormatter)
982 FormatterABC.register(SVGFormatter)
984 FormatterABC.register(SVGFormatter)
983 FormatterABC.register(PNGFormatter)
985 FormatterABC.register(PNGFormatter)
984 FormatterABC.register(PDFFormatter)
986 FormatterABC.register(PDFFormatter)
985 FormatterABC.register(JPEGFormatter)
987 FormatterABC.register(JPEGFormatter)
986 FormatterABC.register(LatexFormatter)
988 FormatterABC.register(LatexFormatter)
987 FormatterABC.register(JSONFormatter)
989 FormatterABC.register(JSONFormatter)
988 FormatterABC.register(JavascriptFormatter)
990 FormatterABC.register(JavascriptFormatter)
989 FormatterABC.register(IPythonDisplayFormatter)
991 FormatterABC.register(IPythonDisplayFormatter)
990 FormatterABC.register(MimeBundleFormatter)
992 FormatterABC.register(MimeBundleFormatter)
991
993
992
994
993 def format_display_data(obj, include=None, exclude=None):
995 def format_display_data(obj, include=None, exclude=None):
994 """Return a format data dict for an object.
996 """Return a format data dict for an object.
995
997
996 By default all format types will be computed.
998 By default all format types will be computed.
997
999
998 Parameters
1000 Parameters
999 ----------
1001 ----------
1000 obj : object
1002 obj : object
1001 The Python object whose format data will be computed.
1003 The Python object whose format data will be computed.
1002
1004
1003 Returns
1005 Returns
1004 -------
1006 -------
1005 format_dict : dict
1007 format_dict : dict
1006 A dictionary of key/value pairs, one or each format that was
1008 A dictionary of key/value pairs, one or each format that was
1007 generated for the object. The keys are the format types, which
1009 generated for the object. The keys are the format types, which
1008 will usually be MIME type strings and the values and JSON'able
1010 will usually be MIME type strings and the values and JSON'able
1009 data structure containing the raw data for the representation in
1011 data structure containing the raw data for the representation in
1010 that format.
1012 that format.
1011 include : list or tuple, optional
1013 include : list or tuple, optional
1012 A list of format type strings (MIME types) to include in the
1014 A list of format type strings (MIME types) to include in the
1013 format data dict. If this is set *only* the format types included
1015 format data dict. If this is set *only* the format types included
1014 in this list will be computed.
1016 in this list will be computed.
1015 exclude : list or tuple, optional
1017 exclude : list or tuple, optional
1016 A list of format type string (MIME types) to exclude in the format
1018 A list of format type string (MIME types) to exclude in the format
1017 data dict. If this is set all format types will be computed,
1019 data dict. If this is set all format types will be computed,
1018 except for those included in this argument.
1020 except for those included in this argument.
1019 """
1021 """
1020 from .interactiveshell import InteractiveShell
1022 from .interactiveshell import InteractiveShell
1021
1023
1022 return InteractiveShell.instance().display_formatter.format(
1024 return InteractiveShell.instance().display_formatter.format(
1023 obj,
1025 obj,
1024 include,
1026 include,
1025 exclude
1027 exclude
1026 )
1028 )
@@ -1,772 +1,773 b''
1 """DEPRECATED: Input handling and transformation machinery.
1 """DEPRECATED: Input handling and transformation machinery.
2
2
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
3 This module was deprecated in IPython 7.0, in favour of inputtransformer2.
4
4
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
5 The first class in this module, :class:`InputSplitter`, is designed to tell when
6 input from a line-oriented frontend is complete and should be executed, and when
6 input from a line-oriented frontend is complete and should be executed, and when
7 the user should be prompted for another line of code instead. The name 'input
7 the user should be prompted for another line of code instead. The name 'input
8 splitter' is largely for historical reasons.
8 splitter' is largely for historical reasons.
9
9
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
10 A companion, :class:`IPythonInputSplitter`, provides the same functionality but
11 with full support for the extended IPython syntax (magics, system calls, etc).
11 with full support for the extended IPython syntax (magics, system calls, etc).
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
12 The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
13 :class:`IPythonInputSplitter` feeds the raw code to the transformers in order
14 and stores the results.
14 and stores the results.
15
15
16 For more details, see the class docstrings below.
16 For more details, see the class docstrings below.
17 """
17 """
18
18
19 from warnings import warn
19 from warnings import warn
20
20
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
21 warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
22 DeprecationWarning)
22 DeprecationWarning)
23
23
24 # Copyright (c) IPython Development Team.
24 # Copyright (c) IPython Development Team.
25 # Distributed under the terms of the Modified BSD License.
25 # Distributed under the terms of the Modified BSD License.
26 import ast
26 import ast
27 import codeop
27 import codeop
28 import io
28 import io
29 import re
29 import re
30 import sys
30 import sys
31 import tokenize
31 import tokenize
32 import warnings
32 import warnings
33
33
34 from typing import List
35
34 from IPython.core.inputtransformer import (leading_indent,
36 from IPython.core.inputtransformer import (leading_indent,
35 classic_prompt,
37 classic_prompt,
36 ipy_prompt,
38 ipy_prompt,
37 cellmagic,
39 cellmagic,
38 assemble_logical_lines,
40 assemble_logical_lines,
39 help_end,
41 help_end,
40 escaped_commands,
42 escaped_commands,
41 assign_from_magic,
43 assign_from_magic,
42 assign_from_system,
44 assign_from_system,
43 assemble_python_lines,
45 assemble_python_lines,
44 )
46 )
45
47
46 # These are available in this module for backwards compatibility.
48 # These are available in this module for backwards compatibility.
47 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
49 from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
48 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
50 ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
49 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
51 ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
50
52
51 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
52 # Utilities
54 # Utilities
53 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
54
56
55 # FIXME: These are general-purpose utilities that later can be moved to the
57 # FIXME: These are general-purpose utilities that later can be moved to the
56 # general ward. Kept here for now because we're being very strict about test
58 # general ward. Kept here for now because we're being very strict about test
57 # coverage with this code, and this lets us ensure that we keep 100% coverage
59 # coverage with this code, and this lets us ensure that we keep 100% coverage
58 # while developing.
60 # while developing.
59
61
60 # compiled regexps for autoindent management
62 # compiled regexps for autoindent management
61 dedent_re = re.compile('|'.join([
63 dedent_re = re.compile('|'.join([
62 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
64 r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
63 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
65 r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
64 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
66 r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
65 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
67 r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
66 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
68 r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
67 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
69 r'^\s+break\s*$', # break (optionally followed by trailing spaces)
68 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
70 r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
69 ]))
71 ]))
70 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
72 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
71
73
72 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
74 # regexp to match pure comment lines so we don't accidentally insert 'if 1:'
73 # before pure comments
75 # before pure comments
74 comment_line_re = re.compile(r'^\s*\#')
76 comment_line_re = re.compile(r'^\s*\#')
75
77
76
78
77 def num_ini_spaces(s):
79 def num_ini_spaces(s):
78 """Return the number of initial spaces in a string.
80 """Return the number of initial spaces in a string.
79
81
80 Note that tabs are counted as a single space. For now, we do *not* support
82 Note that tabs are counted as a single space. For now, we do *not* support
81 mixing of tabs and spaces in the user's input.
83 mixing of tabs and spaces in the user's input.
82
84
83 Parameters
85 Parameters
84 ----------
86 ----------
85 s : string
87 s : string
86
88
87 Returns
89 Returns
88 -------
90 -------
89 n : int
91 n : int
90 """
92 """
91
93
92 ini_spaces = ini_spaces_re.match(s)
94 ini_spaces = ini_spaces_re.match(s)
93 if ini_spaces:
95 if ini_spaces:
94 return ini_spaces.end()
96 return ini_spaces.end()
95 else:
97 else:
96 return 0
98 return 0
97
99
98 # Fake token types for partial_tokenize:
100 # Fake token types for partial_tokenize:
99 INCOMPLETE_STRING = tokenize.N_TOKENS
101 INCOMPLETE_STRING = tokenize.N_TOKENS
100 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
102 IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
101
103
102 # The 2 classes below have the same API as TokenInfo, but don't try to look up
104 # The 2 classes below have the same API as TokenInfo, but don't try to look up
103 # a token type name that they won't find.
105 # a token type name that they won't find.
104 class IncompleteString:
106 class IncompleteString:
105 type = exact_type = INCOMPLETE_STRING
107 type = exact_type = INCOMPLETE_STRING
106 def __init__(self, s, start, end, line):
108 def __init__(self, s, start, end, line):
107 self.s = s
109 self.s = s
108 self.start = start
110 self.start = start
109 self.end = end
111 self.end = end
110 self.line = line
112 self.line = line
111
113
112 class InMultilineStatement:
114 class InMultilineStatement:
113 type = exact_type = IN_MULTILINE_STATEMENT
115 type = exact_type = IN_MULTILINE_STATEMENT
114 def __init__(self, pos, line):
116 def __init__(self, pos, line):
115 self.s = ''
117 self.s = ''
116 self.start = self.end = pos
118 self.start = self.end = pos
117 self.line = line
119 self.line = line
118
120
119 def partial_tokens(s):
121 def partial_tokens(s):
120 """Iterate over tokens from a possibly-incomplete string of code.
122 """Iterate over tokens from a possibly-incomplete string of code.
121
123
122 This adds two special token types: INCOMPLETE_STRING and
124 This adds two special token types: INCOMPLETE_STRING and
123 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
125 IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
124 represent the two main ways for code to be incomplete.
126 represent the two main ways for code to be incomplete.
125 """
127 """
126 readline = io.StringIO(s).readline
128 readline = io.StringIO(s).readline
127 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
129 token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
128 try:
130 try:
129 for token in tokenize.generate_tokens(readline):
131 for token in tokenize.generate_tokens(readline):
130 yield token
132 yield token
131 except tokenize.TokenError as e:
133 except tokenize.TokenError as e:
132 # catch EOF error
134 # catch EOF error
133 lines = s.splitlines(keepends=True)
135 lines = s.splitlines(keepends=True)
134 end = len(lines), len(lines[-1])
136 end = len(lines), len(lines[-1])
135 if 'multi-line string' in e.args[0]:
137 if 'multi-line string' in e.args[0]:
136 l, c = start = token.end
138 l, c = start = token.end
137 s = lines[l-1][c:] + ''.join(lines[l:])
139 s = lines[l-1][c:] + ''.join(lines[l:])
138 yield IncompleteString(s, start, end, lines[-1])
140 yield IncompleteString(s, start, end, lines[-1])
139 elif 'multi-line statement' in e.args[0]:
141 elif 'multi-line statement' in e.args[0]:
140 yield InMultilineStatement(end, lines[-1])
142 yield InMultilineStatement(end, lines[-1])
141 else:
143 else:
142 raise
144 raise
143
145
144 def find_next_indent(code):
146 def find_next_indent(code):
145 """Find the number of spaces for the next line of indentation"""
147 """Find the number of spaces for the next line of indentation"""
146 tokens = list(partial_tokens(code))
148 tokens = list(partial_tokens(code))
147 if tokens[-1].type == tokenize.ENDMARKER:
149 if tokens[-1].type == tokenize.ENDMARKER:
148 tokens.pop()
150 tokens.pop()
149 if not tokens:
151 if not tokens:
150 return 0
152 return 0
151 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
153 while (tokens[-1].type in {tokenize.DEDENT, tokenize.NEWLINE, tokenize.COMMENT}):
152 tokens.pop()
154 tokens.pop()
153
155
154 if tokens[-1].type == INCOMPLETE_STRING:
156 if tokens[-1].type == INCOMPLETE_STRING:
155 # Inside a multiline string
157 # Inside a multiline string
156 return 0
158 return 0
157
159
158 # Find the indents used before
160 # Find the indents used before
159 prev_indents = [0]
161 prev_indents = [0]
160 def _add_indent(n):
162 def _add_indent(n):
161 if n != prev_indents[-1]:
163 if n != prev_indents[-1]:
162 prev_indents.append(n)
164 prev_indents.append(n)
163
165
164 tokiter = iter(tokens)
166 tokiter = iter(tokens)
165 for tok in tokiter:
167 for tok in tokiter:
166 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
168 if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
167 _add_indent(tok.end[1])
169 _add_indent(tok.end[1])
168 elif (tok.type == tokenize.NL):
170 elif (tok.type == tokenize.NL):
169 try:
171 try:
170 _add_indent(next(tokiter).start[1])
172 _add_indent(next(tokiter).start[1])
171 except StopIteration:
173 except StopIteration:
172 break
174 break
173
175
174 last_indent = prev_indents.pop()
176 last_indent = prev_indents.pop()
175
177
176 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
178 # If we've just opened a multiline statement (e.g. 'a = ['), indent more
177 if tokens[-1].type == IN_MULTILINE_STATEMENT:
179 if tokens[-1].type == IN_MULTILINE_STATEMENT:
178 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
180 if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
179 return last_indent + 4
181 return last_indent + 4
180 return last_indent
182 return last_indent
181
183
182 if tokens[-1].exact_type == tokenize.COLON:
184 if tokens[-1].exact_type == tokenize.COLON:
183 # Line ends with colon - indent
185 # Line ends with colon - indent
184 return last_indent + 4
186 return last_indent + 4
185
187
186 if last_indent:
188 if last_indent:
187 # Examine the last line for dedent cues - statements like return or
189 # Examine the last line for dedent cues - statements like return or
188 # raise which normally end a block of code.
190 # raise which normally end a block of code.
189 last_line_starts = 0
191 last_line_starts = 0
190 for i, tok in enumerate(tokens):
192 for i, tok in enumerate(tokens):
191 if tok.type == tokenize.NEWLINE:
193 if tok.type == tokenize.NEWLINE:
192 last_line_starts = i + 1
194 last_line_starts = i + 1
193
195
194 last_line_tokens = tokens[last_line_starts:]
196 last_line_tokens = tokens[last_line_starts:]
195 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
197 names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
196 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
198 if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
197 # Find the most recent indentation less than the current level
199 # Find the most recent indentation less than the current level
198 for indent in reversed(prev_indents):
200 for indent in reversed(prev_indents):
199 if indent < last_indent:
201 if indent < last_indent:
200 return indent
202 return indent
201
203
202 return last_indent
204 return last_indent
203
205
204
206
205 def last_blank(src):
207 def last_blank(src):
206 """Determine if the input source ends in a blank.
208 """Determine if the input source ends in a blank.
207
209
208 A blank is either a newline or a line consisting of whitespace.
210 A blank is either a newline or a line consisting of whitespace.
209
211
210 Parameters
212 Parameters
211 ----------
213 ----------
212 src : string
214 src : string
213 A single or multiline string.
215 A single or multiline string.
214 """
216 """
215 if not src: return False
217 if not src: return False
216 ll = src.splitlines()[-1]
218 ll = src.splitlines()[-1]
217 return (ll == '') or ll.isspace()
219 return (ll == '') or ll.isspace()
218
220
219
221
220 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
222 last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
221 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
223 last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
222
224
223 def last_two_blanks(src):
225 def last_two_blanks(src):
224 """Determine if the input source ends in two blanks.
226 """Determine if the input source ends in two blanks.
225
227
226 A blank is either a newline or a line consisting of whitespace.
228 A blank is either a newline or a line consisting of whitespace.
227
229
228 Parameters
230 Parameters
229 ----------
231 ----------
230 src : string
232 src : string
231 A single or multiline string.
233 A single or multiline string.
232 """
234 """
233 if not src: return False
235 if not src: return False
234 # The logic here is tricky: I couldn't get a regexp to work and pass all
236 # The logic here is tricky: I couldn't get a regexp to work and pass all
235 # the tests, so I took a different approach: split the source by lines,
237 # the tests, so I took a different approach: split the source by lines,
236 # grab the last two and prepend '###\n' as a stand-in for whatever was in
238 # grab the last two and prepend '###\n' as a stand-in for whatever was in
237 # the body before the last two lines. Then, with that structure, it's
239 # the body before the last two lines. Then, with that structure, it's
238 # possible to analyze with two regexps. Not the most elegant solution, but
240 # possible to analyze with two regexps. Not the most elegant solution, but
239 # it works. If anyone tries to change this logic, make sure to validate
241 # it works. If anyone tries to change this logic, make sure to validate
240 # the whole test suite first!
242 # the whole test suite first!
241 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
243 new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
242 return (bool(last_two_blanks_re.match(new_src)) or
244 return (bool(last_two_blanks_re.match(new_src)) or
243 bool(last_two_blanks_re2.match(new_src)) )
245 bool(last_two_blanks_re2.match(new_src)) )
244
246
245
247
246 def remove_comments(src):
248 def remove_comments(src):
247 """Remove all comments from input source.
249 """Remove all comments from input source.
248
250
249 Note: comments are NOT recognized inside of strings!
251 Note: comments are NOT recognized inside of strings!
250
252
251 Parameters
253 Parameters
252 ----------
254 ----------
253 src : string
255 src : string
254 A single or multiline input string.
256 A single or multiline input string.
255
257
256 Returns
258 Returns
257 -------
259 -------
258 String with all Python comments removed.
260 String with all Python comments removed.
259 """
261 """
260
262
261 return re.sub('#.*', '', src)
263 return re.sub('#.*', '', src)
262
264
263
265
264 def get_input_encoding():
266 def get_input_encoding():
265 """Return the default standard input encoding.
267 """Return the default standard input encoding.
266
268
267 If sys.stdin has no encoding, 'ascii' is returned."""
269 If sys.stdin has no encoding, 'ascii' is returned."""
268 # There are strange environments for which sys.stdin.encoding is None. We
270 # There are strange environments for which sys.stdin.encoding is None. We
269 # ensure that a valid encoding is returned.
271 # ensure that a valid encoding is returned.
270 encoding = getattr(sys.stdin, 'encoding', None)
272 encoding = getattr(sys.stdin, 'encoding', None)
271 if encoding is None:
273 if encoding is None:
272 encoding = 'ascii'
274 encoding = 'ascii'
273 return encoding
275 return encoding
274
276
275 #-----------------------------------------------------------------------------
277 #-----------------------------------------------------------------------------
276 # Classes and functions for normal Python syntax handling
278 # Classes and functions for normal Python syntax handling
277 #-----------------------------------------------------------------------------
279 #-----------------------------------------------------------------------------
278
280
279 class InputSplitter(object):
281 class InputSplitter(object):
280 r"""An object that can accumulate lines of Python source before execution.
282 r"""An object that can accumulate lines of Python source before execution.
281
283
282 This object is designed to be fed python source line-by-line, using
284 This object is designed to be fed python source line-by-line, using
283 :meth:`push`. It will return on each push whether the currently pushed
285 :meth:`push`. It will return on each push whether the currently pushed
284 code could be executed already. In addition, it provides a method called
286 code could be executed already. In addition, it provides a method called
285 :meth:`push_accepts_more` that can be used to query whether more input
287 :meth:`push_accepts_more` that can be used to query whether more input
286 can be pushed into a single interactive block.
288 can be pushed into a single interactive block.
287
289
288 This is a simple example of how an interactive terminal-based client can use
290 This is a simple example of how an interactive terminal-based client can use
289 this tool::
291 this tool::
290
292
291 isp = InputSplitter()
293 isp = InputSplitter()
292 while isp.push_accepts_more():
294 while isp.push_accepts_more():
293 indent = ' '*isp.indent_spaces
295 indent = ' '*isp.indent_spaces
294 prompt = '>>> ' + indent
296 prompt = '>>> ' + indent
295 line = indent + raw_input(prompt)
297 line = indent + raw_input(prompt)
296 isp.push(line)
298 isp.push(line)
297 print 'Input source was:\n', isp.source_reset(),
299 print 'Input source was:\n', isp.source_reset(),
298 """
300 """
299 # A cache for storing the current indentation
301 # A cache for storing the current indentation
300 # The first value stores the most recently processed source input
302 # The first value stores the most recently processed source input
301 # The second value is the number of spaces for the current indentation
303 # The second value is the number of spaces for the current indentation
302 # If self.source matches the first value, the second value is a valid
304 # If self.source matches the first value, the second value is a valid
303 # current indentation. Otherwise, the cache is invalid and the indentation
305 # current indentation. Otherwise, the cache is invalid and the indentation
304 # must be recalculated.
306 # must be recalculated.
305 _indent_spaces_cache = None, None
307 _indent_spaces_cache = None, None
306 # String, indicating the default input encoding. It is computed by default
308 # String, indicating the default input encoding. It is computed by default
307 # at initialization time via get_input_encoding(), but it can be reset by a
309 # at initialization time via get_input_encoding(), but it can be reset by a
308 # client with specific knowledge of the encoding.
310 # client with specific knowledge of the encoding.
309 encoding = ''
311 encoding = ''
310 # String where the current full source input is stored, properly encoded.
312 # String where the current full source input is stored, properly encoded.
311 # Reading this attribute is the normal way of querying the currently pushed
313 # Reading this attribute is the normal way of querying the currently pushed
312 # source code, that has been properly encoded.
314 # source code, that has been properly encoded.
313 source = ''
315 source = ''
314 # Code object corresponding to the current source. It is automatically
316 # Code object corresponding to the current source. It is automatically
315 # synced to the source, so it can be queried at any time to obtain the code
317 # synced to the source, so it can be queried at any time to obtain the code
316 # object; it will be None if the source doesn't compile to valid Python.
318 # object; it will be None if the source doesn't compile to valid Python.
317 code = None
319 code = None
318
320
319 # Private attributes
321 # Private attributes
320
322
321 # List with lines of input accumulated so far
323 # List with lines of input accumulated so far
322 _buffer = None
324 _buffer: List[str]
323 # Command compiler
325 # Command compiler
324 _compile = None
326 _compile: codeop.CommandCompiler
325 # Boolean indicating whether the current block is complete
327 # Boolean indicating whether the current block is complete
326 _is_complete = None
328 _is_complete = None
327 # Boolean indicating whether the current block has an unrecoverable syntax error
329 # Boolean indicating whether the current block has an unrecoverable syntax error
328 _is_invalid = False
330 _is_invalid = False
329
331
330 def __init__(self):
332 def __init__(self) -> None:
331 """Create a new InputSplitter instance.
333 """Create a new InputSplitter instance."""
332 """
333 self._buffer = []
334 self._buffer = []
334 self._compile = codeop.CommandCompiler()
335 self._compile = codeop.CommandCompiler()
335 self.encoding = get_input_encoding()
336 self.encoding = get_input_encoding()
336
337
337 def reset(self):
338 def reset(self):
338 """Reset the input buffer and associated state."""
339 """Reset the input buffer and associated state."""
339 self._buffer[:] = []
340 self._buffer[:] = []
340 self.source = ''
341 self.source = ''
341 self.code = None
342 self.code = None
342 self._is_complete = False
343 self._is_complete = False
343 self._is_invalid = False
344 self._is_invalid = False
344
345
345 def source_reset(self):
346 def source_reset(self):
346 """Return the input source and perform a full reset.
347 """Return the input source and perform a full reset.
347 """
348 """
348 out = self.source
349 out = self.source
349 self.reset()
350 self.reset()
350 return out
351 return out
351
352
352 def check_complete(self, source):
353 def check_complete(self, source):
353 """Return whether a block of code is ready to execute, or should be continued
354 """Return whether a block of code is ready to execute, or should be continued
354
355
355 This is a non-stateful API, and will reset the state of this InputSplitter.
356 This is a non-stateful API, and will reset the state of this InputSplitter.
356
357
357 Parameters
358 Parameters
358 ----------
359 ----------
359 source : string
360 source : string
360 Python input code, which can be multiline.
361 Python input code, which can be multiline.
361
362
362 Returns
363 Returns
363 -------
364 -------
364 status : str
365 status : str
365 One of 'complete', 'incomplete', or 'invalid' if source is not a
366 One of 'complete', 'incomplete', or 'invalid' if source is not a
366 prefix of valid code.
367 prefix of valid code.
367 indent_spaces : int or None
368 indent_spaces : int or None
368 The number of spaces by which to indent the next line of code. If
369 The number of spaces by which to indent the next line of code. If
369 status is not 'incomplete', this is None.
370 status is not 'incomplete', this is None.
370 """
371 """
371 self.reset()
372 self.reset()
372 try:
373 try:
373 self.push(source)
374 self.push(source)
374 except SyntaxError:
375 except SyntaxError:
375 # Transformers in IPythonInputSplitter can raise SyntaxError,
376 # Transformers in IPythonInputSplitter can raise SyntaxError,
376 # which push() will not catch.
377 # which push() will not catch.
377 return 'invalid', None
378 return 'invalid', None
378 else:
379 else:
379 if self._is_invalid:
380 if self._is_invalid:
380 return 'invalid', None
381 return 'invalid', None
381 elif self.push_accepts_more():
382 elif self.push_accepts_more():
382 return 'incomplete', self.get_indent_spaces()
383 return 'incomplete', self.get_indent_spaces()
383 else:
384 else:
384 return 'complete', None
385 return 'complete', None
385 finally:
386 finally:
386 self.reset()
387 self.reset()
387
388
388 def push(self, lines:str) -> bool:
389 def push(self, lines:str) -> bool:
389 """Push one or more lines of input.
390 """Push one or more lines of input.
390
391
391 This stores the given lines and returns a status code indicating
392 This stores the given lines and returns a status code indicating
392 whether the code forms a complete Python block or not.
393 whether the code forms a complete Python block or not.
393
394
394 Any exceptions generated in compilation are swallowed, but if an
395 Any exceptions generated in compilation are swallowed, but if an
395 exception was produced, the method returns True.
396 exception was produced, the method returns True.
396
397
397 Parameters
398 Parameters
398 ----------
399 ----------
399 lines : string
400 lines : string
400 One or more lines of Python input.
401 One or more lines of Python input.
401
402
402 Returns
403 Returns
403 -------
404 -------
404 is_complete : boolean
405 is_complete : boolean
405 True if the current input source (the result of the current input
406 True if the current input source (the result of the current input
406 plus prior inputs) forms a complete Python execution block. Note that
407 plus prior inputs) forms a complete Python execution block. Note that
407 this value is also stored as a private attribute (``_is_complete``), so it
408 this value is also stored as a private attribute (``_is_complete``), so it
408 can be queried at any time.
409 can be queried at any time.
409 """
410 """
410 assert isinstance(lines, str)
411 assert isinstance(lines, str)
411 self._store(lines)
412 self._store(lines)
412 source = self.source
413 source = self.source
413
414
414 # Before calling _compile(), reset the code object to None so that if an
415 # Before calling _compile(), reset the code object to None so that if an
415 # exception is raised in compilation, we don't mislead by having
416 # exception is raised in compilation, we don't mislead by having
416 # inconsistent code/source attributes.
417 # inconsistent code/source attributes.
417 self.code, self._is_complete = None, None
418 self.code, self._is_complete = None, None
418 self._is_invalid = False
419 self._is_invalid = False
419
420
420 # Honor termination lines properly
421 # Honor termination lines properly
421 if source.endswith('\\\n'):
422 if source.endswith('\\\n'):
422 return False
423 return False
423
424
424 try:
425 try:
425 with warnings.catch_warnings():
426 with warnings.catch_warnings():
426 warnings.simplefilter('error', SyntaxWarning)
427 warnings.simplefilter('error', SyntaxWarning)
427 self.code = self._compile(source, symbol="exec")
428 self.code = self._compile(source, symbol="exec")
428 # Invalid syntax can produce any of a number of different errors from
429 # Invalid syntax can produce any of a number of different errors from
429 # inside the compiler, so we have to catch them all. Syntax errors
430 # inside the compiler, so we have to catch them all. Syntax errors
430 # immediately produce a 'ready' block, so the invalid Python can be
431 # immediately produce a 'ready' block, so the invalid Python can be
431 # sent to the kernel for evaluation with possible ipython
432 # sent to the kernel for evaluation with possible ipython
432 # special-syntax conversion.
433 # special-syntax conversion.
433 except (SyntaxError, OverflowError, ValueError, TypeError,
434 except (SyntaxError, OverflowError, ValueError, TypeError,
434 MemoryError, SyntaxWarning):
435 MemoryError, SyntaxWarning):
435 self._is_complete = True
436 self._is_complete = True
436 self._is_invalid = True
437 self._is_invalid = True
437 else:
438 else:
438 # Compilation didn't produce any exceptions (though it may not have
439 # Compilation didn't produce any exceptions (though it may not have
439 # given a complete code object)
440 # given a complete code object)
440 self._is_complete = self.code is not None
441 self._is_complete = self.code is not None
441
442
442 return self._is_complete
443 return self._is_complete
443
444
444 def push_accepts_more(self):
445 def push_accepts_more(self):
445 """Return whether a block of interactive input can accept more input.
446 """Return whether a block of interactive input can accept more input.
446
447
447 This method is meant to be used by line-oriented frontends, who need to
448 This method is meant to be used by line-oriented frontends, who need to
448 guess whether a block is complete or not based solely on prior and
449 guess whether a block is complete or not based solely on prior and
449 current input lines. The InputSplitter considers it has a complete
450 current input lines. The InputSplitter considers it has a complete
450 interactive block and will not accept more input when either:
451 interactive block and will not accept more input when either:
451
452
452 * A SyntaxError is raised
453 * A SyntaxError is raised
453
454
454 * The code is complete and consists of a single line or a single
455 * The code is complete and consists of a single line or a single
455 non-compound statement
456 non-compound statement
456
457
457 * The code is complete and has a blank line at the end
458 * The code is complete and has a blank line at the end
458
459
459 If the current input produces a syntax error, this method immediately
460 If the current input produces a syntax error, this method immediately
460 returns False but does *not* raise the syntax error exception, as
461 returns False but does *not* raise the syntax error exception, as
461 typically clients will want to send invalid syntax to an execution
462 typically clients will want to send invalid syntax to an execution
462 backend which might convert the invalid syntax into valid Python via
463 backend which might convert the invalid syntax into valid Python via
463 one of the dynamic IPython mechanisms.
464 one of the dynamic IPython mechanisms.
464 """
465 """
465
466
466 # With incomplete input, unconditionally accept more
467 # With incomplete input, unconditionally accept more
467 # A syntax error also sets _is_complete to True - see push()
468 # A syntax error also sets _is_complete to True - see push()
468 if not self._is_complete:
469 if not self._is_complete:
469 #print("Not complete") # debug
470 #print("Not complete") # debug
470 return True
471 return True
471
472
472 # The user can make any (complete) input execute by leaving a blank line
473 # The user can make any (complete) input execute by leaving a blank line
473 last_line = self.source.splitlines()[-1]
474 last_line = self.source.splitlines()[-1]
474 if (not last_line) or last_line.isspace():
475 if (not last_line) or last_line.isspace():
475 #print("Blank line") # debug
476 #print("Blank line") # debug
476 return False
477 return False
477
478
478 # If there's just a single line or AST node, and we're flush left, as is
479 # If there's just a single line or AST node, and we're flush left, as is
479 # the case after a simple statement such as 'a=1', we want to execute it
480 # the case after a simple statement such as 'a=1', we want to execute it
480 # straight away.
481 # straight away.
481 if self.get_indent_spaces() == 0:
482 if self.get_indent_spaces() == 0:
482 if len(self.source.splitlines()) <= 1:
483 if len(self.source.splitlines()) <= 1:
483 return False
484 return False
484
485
485 try:
486 try:
486 code_ast = ast.parse(u''.join(self._buffer))
487 code_ast = ast.parse("".join(self._buffer))
487 except Exception:
488 except Exception:
488 #print("Can't parse AST") # debug
489 #print("Can't parse AST") # debug
489 return False
490 return False
490 else:
491 else:
491 if len(code_ast.body) == 1 and \
492 if len(code_ast.body) == 1 and \
492 not hasattr(code_ast.body[0], 'body'):
493 not hasattr(code_ast.body[0], 'body'):
493 #print("Simple statement") # debug
494 #print("Simple statement") # debug
494 return False
495 return False
495
496
496 # General fallback - accept more code
497 # General fallback - accept more code
497 return True
498 return True
498
499
499 def get_indent_spaces(self):
500 def get_indent_spaces(self):
500 sourcefor, n = self._indent_spaces_cache
501 sourcefor, n = self._indent_spaces_cache
501 if sourcefor == self.source:
502 if sourcefor == self.source:
502 return n
503 return n
503
504
504 # self.source always has a trailing newline
505 # self.source always has a trailing newline
505 n = find_next_indent(self.source[:-1])
506 n = find_next_indent(self.source[:-1])
506 self._indent_spaces_cache = (self.source, n)
507 self._indent_spaces_cache = (self.source, n)
507 return n
508 return n
508
509
509 # Backwards compatibility. I think all code that used .indent_spaces was
510 # Backwards compatibility. I think all code that used .indent_spaces was
510 # inside IPython, but we can leave this here until IPython 7 in case any
511 # inside IPython, but we can leave this here until IPython 7 in case any
511 # other modules are using it. -TK, November 2017
512 # other modules are using it. -TK, November 2017
512 indent_spaces = property(get_indent_spaces)
513 indent_spaces = property(get_indent_spaces)
513
514
514 def _store(self, lines, buffer=None, store='source'):
515 def _store(self, lines, buffer=None, store='source'):
515 """Store one or more lines of input.
516 """Store one or more lines of input.
516
517
517 If input lines are not newline-terminated, a newline is automatically
518 If input lines are not newline-terminated, a newline is automatically
518 appended."""
519 appended."""
519
520
520 if buffer is None:
521 if buffer is None:
521 buffer = self._buffer
522 buffer = self._buffer
522
523
523 if lines.endswith('\n'):
524 if lines.endswith('\n'):
524 buffer.append(lines)
525 buffer.append(lines)
525 else:
526 else:
526 buffer.append(lines+'\n')
527 buffer.append(lines+'\n')
527 setattr(self, store, self._set_source(buffer))
528 setattr(self, store, self._set_source(buffer))
528
529
529 def _set_source(self, buffer):
530 def _set_source(self, buffer):
530 return u''.join(buffer)
531 return u''.join(buffer)
531
532
532
533
533 class IPythonInputSplitter(InputSplitter):
534 class IPythonInputSplitter(InputSplitter):
534 """An input splitter that recognizes all of IPython's special syntax."""
535 """An input splitter that recognizes all of IPython's special syntax."""
535
536
536 # String with raw, untransformed input.
537 # String with raw, untransformed input.
537 source_raw = ''
538 source_raw = ''
538
539
539 # Flag to track when a transformer has stored input that it hasn't given
540 # Flag to track when a transformer has stored input that it hasn't given
540 # back yet.
541 # back yet.
541 transformer_accumulating = False
542 transformer_accumulating = False
542
543
543 # Flag to track when assemble_python_lines has stored input that it hasn't
544 # Flag to track when assemble_python_lines has stored input that it hasn't
544 # given back yet.
545 # given back yet.
545 within_python_line = False
546 within_python_line = False
546
547
547 # Private attributes
548 # Private attributes
548
549
549 # List with lines of raw input accumulated so far.
550 # List with lines of raw input accumulated so far.
550 _buffer_raw = None
551 _buffer_raw = None
551
552
552 def __init__(self, line_input_checker=True, physical_line_transforms=None,
553 def __init__(self, line_input_checker=True, physical_line_transforms=None,
553 logical_line_transforms=None, python_line_transforms=None):
554 logical_line_transforms=None, python_line_transforms=None):
554 super(IPythonInputSplitter, self).__init__()
555 super(IPythonInputSplitter, self).__init__()
555 self._buffer_raw = []
556 self._buffer_raw = []
556 self._validate = True
557 self._validate = True
557
558
558 if physical_line_transforms is not None:
559 if physical_line_transforms is not None:
559 self.physical_line_transforms = physical_line_transforms
560 self.physical_line_transforms = physical_line_transforms
560 else:
561 else:
561 self.physical_line_transforms = [
562 self.physical_line_transforms = [
562 leading_indent(),
563 leading_indent(),
563 classic_prompt(),
564 classic_prompt(),
564 ipy_prompt(),
565 ipy_prompt(),
565 cellmagic(end_on_blank_line=line_input_checker),
566 cellmagic(end_on_blank_line=line_input_checker),
566 ]
567 ]
567
568
568 self.assemble_logical_lines = assemble_logical_lines()
569 self.assemble_logical_lines = assemble_logical_lines()
569 if logical_line_transforms is not None:
570 if logical_line_transforms is not None:
570 self.logical_line_transforms = logical_line_transforms
571 self.logical_line_transforms = logical_line_transforms
571 else:
572 else:
572 self.logical_line_transforms = [
573 self.logical_line_transforms = [
573 help_end(),
574 help_end(),
574 escaped_commands(),
575 escaped_commands(),
575 assign_from_magic(),
576 assign_from_magic(),
576 assign_from_system(),
577 assign_from_system(),
577 ]
578 ]
578
579
579 self.assemble_python_lines = assemble_python_lines()
580 self.assemble_python_lines = assemble_python_lines()
580 if python_line_transforms is not None:
581 if python_line_transforms is not None:
581 self.python_line_transforms = python_line_transforms
582 self.python_line_transforms = python_line_transforms
582 else:
583 else:
583 # We don't use any of these at present
584 # We don't use any of these at present
584 self.python_line_transforms = []
585 self.python_line_transforms = []
585
586
586 @property
587 @property
587 def transforms(self):
588 def transforms(self):
588 "Quick access to all transformers."
589 "Quick access to all transformers."
589 return self.physical_line_transforms + \
590 return self.physical_line_transforms + \
590 [self.assemble_logical_lines] + self.logical_line_transforms + \
591 [self.assemble_logical_lines] + self.logical_line_transforms + \
591 [self.assemble_python_lines] + self.python_line_transforms
592 [self.assemble_python_lines] + self.python_line_transforms
592
593
593 @property
594 @property
594 def transforms_in_use(self):
595 def transforms_in_use(self):
595 """Transformers, excluding logical line transformers if we're in a
596 """Transformers, excluding logical line transformers if we're in a
596 Python line."""
597 Python line."""
597 t = self.physical_line_transforms[:]
598 t = self.physical_line_transforms[:]
598 if not self.within_python_line:
599 if not self.within_python_line:
599 t += [self.assemble_logical_lines] + self.logical_line_transforms
600 t += [self.assemble_logical_lines] + self.logical_line_transforms
600 return t + [self.assemble_python_lines] + self.python_line_transforms
601 return t + [self.assemble_python_lines] + self.python_line_transforms
601
602
602 def reset(self):
603 def reset(self):
603 """Reset the input buffer and associated state."""
604 """Reset the input buffer and associated state."""
604 super(IPythonInputSplitter, self).reset()
605 super(IPythonInputSplitter, self).reset()
605 self._buffer_raw[:] = []
606 self._buffer_raw[:] = []
606 self.source_raw = ''
607 self.source_raw = ''
607 self.transformer_accumulating = False
608 self.transformer_accumulating = False
608 self.within_python_line = False
609 self.within_python_line = False
609
610
610 for t in self.transforms:
611 for t in self.transforms:
611 try:
612 try:
612 t.reset()
613 t.reset()
613 except SyntaxError:
614 except SyntaxError:
614 # Nothing that calls reset() expects to handle transformer
615 # Nothing that calls reset() expects to handle transformer
615 # errors
616 # errors
616 pass
617 pass
617
618
618 def flush_transformers(self):
619 def flush_transformers(self):
619 def _flush(transform, outs):
620 def _flush(transform, outs):
620 """yield transformed lines
621 """yield transformed lines
621
622
622 always strings, never None
623 always strings, never None
623
624
624 transform: the current transform
625 transform: the current transform
625 outs: an iterable of previously transformed inputs.
626 outs: an iterable of previously transformed inputs.
626 Each may be multiline, which will be passed
627 Each may be multiline, which will be passed
627 one line at a time to transform.
628 one line at a time to transform.
628 """
629 """
629 for out in outs:
630 for out in outs:
630 for line in out.splitlines():
631 for line in out.splitlines():
631 # push one line at a time
632 # push one line at a time
632 tmp = transform.push(line)
633 tmp = transform.push(line)
633 if tmp is not None:
634 if tmp is not None:
634 yield tmp
635 yield tmp
635
636
636 # reset the transform
637 # reset the transform
637 tmp = transform.reset()
638 tmp = transform.reset()
638 if tmp is not None:
639 if tmp is not None:
639 yield tmp
640 yield tmp
640
641
641 out = []
642 out = []
642 for t in self.transforms_in_use:
643 for t in self.transforms_in_use:
643 out = _flush(t, out)
644 out = _flush(t, out)
644
645
645 out = list(out)
646 out = list(out)
646 if out:
647 if out:
647 self._store('\n'.join(out))
648 self._store('\n'.join(out))
648
649
649 def raw_reset(self):
650 def raw_reset(self):
650 """Return raw input only and perform a full reset.
651 """Return raw input only and perform a full reset.
651 """
652 """
652 out = self.source_raw
653 out = self.source_raw
653 self.reset()
654 self.reset()
654 return out
655 return out
655
656
656 def source_reset(self):
657 def source_reset(self):
657 try:
658 try:
658 self.flush_transformers()
659 self.flush_transformers()
659 return self.source
660 return self.source
660 finally:
661 finally:
661 self.reset()
662 self.reset()
662
663
663 def push_accepts_more(self):
664 def push_accepts_more(self):
664 if self.transformer_accumulating:
665 if self.transformer_accumulating:
665 return True
666 return True
666 else:
667 else:
667 return super(IPythonInputSplitter, self).push_accepts_more()
668 return super(IPythonInputSplitter, self).push_accepts_more()
668
669
669 def transform_cell(self, cell):
670 def transform_cell(self, cell):
670 """Process and translate a cell of input.
671 """Process and translate a cell of input.
671 """
672 """
672 self.reset()
673 self.reset()
673 try:
674 try:
674 self.push(cell)
675 self.push(cell)
675 self.flush_transformers()
676 self.flush_transformers()
676 return self.source
677 return self.source
677 finally:
678 finally:
678 self.reset()
679 self.reset()
679
680
680 def push(self, lines:str) -> bool:
681 def push(self, lines:str) -> bool:
681 """Push one or more lines of IPython input.
682 """Push one or more lines of IPython input.
682
683
683 This stores the given lines and returns a status code indicating
684 This stores the given lines and returns a status code indicating
684 whether the code forms a complete Python block or not, after processing
685 whether the code forms a complete Python block or not, after processing
685 all input lines for special IPython syntax.
686 all input lines for special IPython syntax.
686
687
687 Any exceptions generated in compilation are swallowed, but if an
688 Any exceptions generated in compilation are swallowed, but if an
688 exception was produced, the method returns True.
689 exception was produced, the method returns True.
689
690
690 Parameters
691 Parameters
691 ----------
692 ----------
692 lines : string
693 lines : string
693 One or more lines of Python input.
694 One or more lines of Python input.
694
695
695 Returns
696 Returns
696 -------
697 -------
697 is_complete : boolean
698 is_complete : boolean
698 True if the current input source (the result of the current input
699 True if the current input source (the result of the current input
699 plus prior inputs) forms a complete Python execution block. Note that
700 plus prior inputs) forms a complete Python execution block. Note that
700 this value is also stored as a private attribute (_is_complete), so it
701 this value is also stored as a private attribute (_is_complete), so it
701 can be queried at any time.
702 can be queried at any time.
702 """
703 """
703 assert isinstance(lines, str)
704 assert isinstance(lines, str)
704 # We must ensure all input is pure unicode
705 # We must ensure all input is pure unicode
705 # ''.splitlines() --> [], but we need to push the empty line to transformers
706 # ''.splitlines() --> [], but we need to push the empty line to transformers
706 lines_list = lines.splitlines()
707 lines_list = lines.splitlines()
707 if not lines_list:
708 if not lines_list:
708 lines_list = ['']
709 lines_list = ['']
709
710
710 # Store raw source before applying any transformations to it. Note
711 # Store raw source before applying any transformations to it. Note
711 # that this must be done *after* the reset() call that would otherwise
712 # that this must be done *after* the reset() call that would otherwise
712 # flush the buffer.
713 # flush the buffer.
713 self._store(lines, self._buffer_raw, 'source_raw')
714 self._store(lines, self._buffer_raw, 'source_raw')
714
715
715 transformed_lines_list = []
716 transformed_lines_list = []
716 for line in lines_list:
717 for line in lines_list:
717 transformed = self._transform_line(line)
718 transformed = self._transform_line(line)
718 if transformed is not None:
719 if transformed is not None:
719 transformed_lines_list.append(transformed)
720 transformed_lines_list.append(transformed)
720
721
721 if transformed_lines_list:
722 if transformed_lines_list:
722 transformed_lines = '\n'.join(transformed_lines_list)
723 transformed_lines = '\n'.join(transformed_lines_list)
723 return super(IPythonInputSplitter, self).push(transformed_lines)
724 return super(IPythonInputSplitter, self).push(transformed_lines)
724 else:
725 else:
725 # Got nothing back from transformers - they must be waiting for
726 # Got nothing back from transformers - they must be waiting for
726 # more input.
727 # more input.
727 return False
728 return False
728
729
729 def _transform_line(self, line):
730 def _transform_line(self, line):
730 """Push a line of input code through the various transformers.
731 """Push a line of input code through the various transformers.
731
732
732 Returns any output from the transformers, or None if a transformer
733 Returns any output from the transformers, or None if a transformer
733 is accumulating lines.
734 is accumulating lines.
734
735
735 Sets self.transformer_accumulating as a side effect.
736 Sets self.transformer_accumulating as a side effect.
736 """
737 """
737 def _accumulating(dbg):
738 def _accumulating(dbg):
738 #print(dbg)
739 #print(dbg)
739 self.transformer_accumulating = True
740 self.transformer_accumulating = True
740 return None
741 return None
741
742
742 for transformer in self.physical_line_transforms:
743 for transformer in self.physical_line_transforms:
743 line = transformer.push(line)
744 line = transformer.push(line)
744 if line is None:
745 if line is None:
745 return _accumulating(transformer)
746 return _accumulating(transformer)
746
747
747 if not self.within_python_line:
748 if not self.within_python_line:
748 line = self.assemble_logical_lines.push(line)
749 line = self.assemble_logical_lines.push(line)
749 if line is None:
750 if line is None:
750 return _accumulating('acc logical line')
751 return _accumulating('acc logical line')
751
752
752 for transformer in self.logical_line_transforms:
753 for transformer in self.logical_line_transforms:
753 line = transformer.push(line)
754 line = transformer.push(line)
754 if line is None:
755 if line is None:
755 return _accumulating(transformer)
756 return _accumulating(transformer)
756
757
757 line = self.assemble_python_lines.push(line)
758 line = self.assemble_python_lines.push(line)
758 if line is None:
759 if line is None:
759 self.within_python_line = True
760 self.within_python_line = True
760 return _accumulating('acc python line')
761 return _accumulating('acc python line')
761 else:
762 else:
762 self.within_python_line = False
763 self.within_python_line = False
763
764
764 for transformer in self.python_line_transforms:
765 for transformer in self.python_line_transforms:
765 line = transformer.push(line)
766 line = transformer.push(line)
766 if line is None:
767 if line is None:
767 return _accumulating(transformer)
768 return _accumulating(transformer)
768
769
769 #print("transformers clear") #debug
770 #print("transformers clear") #debug
770 self.transformer_accumulating = False
771 self.transformer_accumulating = False
771 return line
772 return line
772
773
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,310 +1,310 b''
1 ''' A decorator-based method of constructing IPython magics with `argparse`
1 ''' A decorator-based method of constructing IPython magics with `argparse`
2 option handling.
2 option handling.
3
3
4 New magic functions can be defined like so::
4 New magic functions can be defined like so::
5
5
6 from IPython.core.magic_arguments import (argument, magic_arguments,
6 from IPython.core.magic_arguments import (argument, magic_arguments,
7 parse_argstring)
7 parse_argstring)
8
8
9 @magic_arguments()
9 @magic_arguments()
10 @argument('-o', '--option', help='An optional argument.')
10 @argument('-o', '--option', help='An optional argument.')
11 @argument('arg', type=int, help='An integer positional argument.')
11 @argument('arg', type=int, help='An integer positional argument.')
12 def magic_cool(self, arg):
12 def magic_cool(self, arg):
13 """ A really cool magic command.
13 """ A really cool magic command.
14
14
15 """
15 """
16 args = parse_argstring(magic_cool, arg)
16 args = parse_argstring(magic_cool, arg)
17 ...
17 ...
18
18
19 The `@magic_arguments` decorator marks the function as having argparse arguments.
19 The `@magic_arguments` decorator marks the function as having argparse arguments.
20 The `@argument` decorator adds an argument using the same syntax as argparse's
20 The `@argument` decorator adds an argument using the same syntax as argparse's
21 `add_argument()` method. More sophisticated uses may also require the
21 `add_argument()` method. More sophisticated uses may also require the
22 `@argument_group` or `@kwds` decorator to customize the formatting and the
22 `@argument_group` or `@kwds` decorator to customize the formatting and the
23 parsing.
23 parsing.
24
24
25 Help text for the magic is automatically generated from the docstring and the
25 Help text for the magic is automatically generated from the docstring and the
26 arguments::
26 arguments::
27
27
28 In[1]: %cool?
28 In[1]: %cool?
29 %cool [-o OPTION] arg
29 %cool [-o OPTION] arg
30
30
31 A really cool magic command.
31 A really cool magic command.
32
32
33 positional arguments:
33 positional arguments:
34 arg An integer positional argument.
34 arg An integer positional argument.
35
35
36 optional arguments:
36 optional arguments:
37 -o OPTION, --option OPTION
37 -o OPTION, --option OPTION
38 An optional argument.
38 An optional argument.
39
39
40 Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic::
40 Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic::
41
41
42 from IPython.core.magic import register_cell_magic
42 from IPython.core.magic import register_cell_magic
43 from IPython.core.magic_arguments import (argument, magic_arguments,
43 from IPython.core.magic_arguments import (argument, magic_arguments,
44 parse_argstring)
44 parse_argstring)
45
45
46
46
47 @magic_arguments()
47 @magic_arguments()
48 @argument(
48 @argument(
49 "--option",
49 "--option",
50 "-o",
50 "-o",
51 help=("Add an option here"),
51 help=("Add an option here"),
52 )
52 )
53 @argument(
53 @argument(
54 "--style",
54 "--style",
55 "-s",
55 "-s",
56 default="foo",
56 default="foo",
57 help=("Add some style arguments"),
57 help=("Add some style arguments"),
58 )
58 )
59 @register_cell_magic
59 @register_cell_magic
60 def my_cell_magic(line, cell):
60 def my_cell_magic(line, cell):
61 args = parse_argstring(my_cell_magic, line)
61 args = parse_argstring(my_cell_magic, line)
62 print(f"{args.option=}")
62 print(f"{args.option=}")
63 print(f"{args.style=}")
63 print(f"{args.style=}")
64 print(f"{cell=}")
64 print(f"{cell=}")
65
65
66 In a jupyter notebook, this cell magic can be executed like this::
66 In a jupyter notebook, this cell magic can be executed like this::
67
67
68 %%my_cell_magic -o Hello
68 %%my_cell_magic -o Hello
69 print("bar")
69 print("bar")
70 i = 42
70 i = 42
71
71
72 Inheritance diagram:
72 Inheritance diagram:
73
73
74 .. inheritance-diagram:: IPython.core.magic_arguments
74 .. inheritance-diagram:: IPython.core.magic_arguments
75 :parts: 3
75 :parts: 3
76
76
77 '''
77 '''
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79 # Copyright (C) 2010-2011, IPython Development Team.
79 # Copyright (C) 2010-2011, IPython Development Team.
80 #
80 #
81 # Distributed under the terms of the Modified BSD License.
81 # Distributed under the terms of the Modified BSD License.
82 #
82 #
83 # The full license is in the file COPYING.txt, distributed with this software.
83 # The full license is in the file COPYING.txt, distributed with this software.
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 import argparse
85 import argparse
86 import re
86 import re
87
87
88 # Our own imports
88 # Our own imports
89 from IPython.core.error import UsageError
89 from IPython.core.error import UsageError
90 from IPython.utils.decorators import undoc
90 from IPython.utils.decorators import undoc
91 from IPython.utils.process import arg_split
91 from IPython.utils.process import arg_split
92 from IPython.utils.text import dedent
92 from IPython.utils.text import dedent
93
93
94 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
94 NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
95
95
96 @undoc
96 @undoc
97 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
97 class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
98 """A HelpFormatter with a couple of changes to meet our needs.
98 """A HelpFormatter with a couple of changes to meet our needs.
99 """
99 """
100 # Modified to dedent text.
100 # Modified to dedent text.
101 def _fill_text(self, text, width, indent):
101 def _fill_text(self, text, width, indent):
102 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
102 return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
103
103
104 # Modified to wrap argument placeholders in <> where necessary.
104 # Modified to wrap argument placeholders in <> where necessary.
105 def _format_action_invocation(self, action):
105 def _format_action_invocation(self, action):
106 if not action.option_strings:
106 if not action.option_strings:
107 metavar, = self._metavar_formatter(action, action.dest)(1)
107 metavar, = self._metavar_formatter(action, action.dest)(1)
108 return metavar
108 return metavar
109
109
110 else:
110 else:
111 parts = []
111 parts = []
112
112
113 # if the Optional doesn't take a value, format is:
113 # if the Optional doesn't take a value, format is:
114 # -s, --long
114 # -s, --long
115 if action.nargs == 0:
115 if action.nargs == 0:
116 parts.extend(action.option_strings)
116 parts.extend(action.option_strings)
117
117
118 # if the Optional takes a value, format is:
118 # if the Optional takes a value, format is:
119 # -s ARGS, --long ARGS
119 # -s ARGS, --long ARGS
120 else:
120 else:
121 default = action.dest.upper()
121 default = action.dest.upper()
122 args_string = self._format_args(action, default)
122 args_string = self._format_args(action, default)
123 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
123 # IPYTHON MODIFICATION: If args_string is not a plain name, wrap
124 # it in <> so it's valid RST.
124 # it in <> so it's valid RST.
125 if not NAME_RE.match(args_string):
125 if not NAME_RE.match(args_string):
126 args_string = "<%s>" % args_string
126 args_string = "<%s>" % args_string
127 for option_string in action.option_strings:
127 for option_string in action.option_strings:
128 parts.append('%s %s' % (option_string, args_string))
128 parts.append('%s %s' % (option_string, args_string))
129
129
130 return ', '.join(parts)
130 return ', '.join(parts)
131
131
132 # Override the default prefix ('usage') to our % magic escape,
132 # Override the default prefix ('usage') to our % magic escape,
133 # in a code block.
133 # in a code block.
134 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
134 def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
135 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
135 super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
136
136
137 class MagicArgumentParser(argparse.ArgumentParser):
137 class MagicArgumentParser(argparse.ArgumentParser):
138 """ An ArgumentParser tweaked for use by IPython magics.
138 """ An ArgumentParser tweaked for use by IPython magics.
139 """
139 """
140 def __init__(self,
140 def __init__(self,
141 prog=None,
141 prog=None,
142 usage=None,
142 usage=None,
143 description=None,
143 description=None,
144 epilog=None,
144 epilog=None,
145 parents=None,
145 parents=None,
146 formatter_class=MagicHelpFormatter,
146 formatter_class=MagicHelpFormatter,
147 prefix_chars='-',
147 prefix_chars='-',
148 argument_default=None,
148 argument_default=None,
149 conflict_handler='error',
149 conflict_handler='error',
150 add_help=False):
150 add_help=False):
151 if parents is None:
151 if parents is None:
152 parents = []
152 parents = []
153 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
153 super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
154 description=description, epilog=epilog,
154 description=description, epilog=epilog,
155 parents=parents, formatter_class=formatter_class,
155 parents=parents, formatter_class=formatter_class,
156 prefix_chars=prefix_chars, argument_default=argument_default,
156 prefix_chars=prefix_chars, argument_default=argument_default,
157 conflict_handler=conflict_handler, add_help=add_help)
157 conflict_handler=conflict_handler, add_help=add_help)
158
158
159 def error(self, message):
159 def error(self, message):
160 """ Raise a catchable error instead of exiting.
160 """ Raise a catchable error instead of exiting.
161 """
161 """
162 raise UsageError(message)
162 raise UsageError(message)
163
163
164 def parse_argstring(self, argstring):
164 def parse_argstring(self, argstring):
165 """ Split a string into an argument list and parse that argument list.
165 """ Split a string into an argument list and parse that argument list.
166 """
166 """
167 argv = arg_split(argstring)
167 argv = arg_split(argstring)
168 return self.parse_args(argv)
168 return self.parse_args(argv)
169
169
170
170
171 def construct_parser(magic_func):
171 def construct_parser(magic_func):
172 """ Construct an argument parser using the function decorations.
172 """ Construct an argument parser using the function decorations.
173 """
173 """
174 kwds = getattr(magic_func, 'argcmd_kwds', {})
174 kwds = getattr(magic_func, 'argcmd_kwds', {})
175 if 'description' not in kwds:
175 if 'description' not in kwds:
176 kwds['description'] = getattr(magic_func, '__doc__', None)
176 kwds['description'] = getattr(magic_func, '__doc__', None)
177 arg_name = real_name(magic_func)
177 arg_name = real_name(magic_func)
178 parser = MagicArgumentParser(arg_name, **kwds)
178 parser = MagicArgumentParser(arg_name, **kwds)
179 # Reverse the list of decorators in order to apply them in the
179 # Reverse the list of decorators in order to apply them in the
180 # order in which they appear in the source.
180 # order in which they appear in the source.
181 group = None
181 group = None
182 for deco in magic_func.decorators[::-1]:
182 for deco in magic_func.decorators[::-1]:
183 result = deco.add_to_parser(parser, group)
183 result = deco.add_to_parser(parser, group)
184 if result is not None:
184 if result is not None:
185 group = result
185 group = result
186
186
187 # Replace the magic function's docstring with the full help text.
187 # Replace the magic function's docstring with the full help text.
188 magic_func.__doc__ = parser.format_help()
188 magic_func.__doc__ = parser.format_help()
189
189
190 return parser
190 return parser
191
191
192
192
193 def parse_argstring(magic_func, argstring):
193 def parse_argstring(magic_func, argstring):
194 """ Parse the string of arguments for the given magic function.
194 """ Parse the string of arguments for the given magic function.
195 """
195 """
196 return magic_func.parser.parse_argstring(argstring)
196 return magic_func.parser.parse_argstring(argstring)
197
197
198
198
199 def real_name(magic_func):
199 def real_name(magic_func):
200 """ Find the real name of the magic.
200 """ Find the real name of the magic.
201 """
201 """
202 magic_name = magic_func.__name__
202 magic_name = magic_func.__name__
203 if magic_name.startswith('magic_'):
203 if magic_name.startswith('magic_'):
204 magic_name = magic_name[len('magic_'):]
204 magic_name = magic_name[len('magic_'):]
205 return getattr(magic_func, 'argcmd_name', magic_name)
205 return getattr(magic_func, 'argcmd_name', magic_name)
206
206
207
207
208 class ArgDecorator(object):
208 class ArgDecorator(object):
209 """ Base class for decorators to add ArgumentParser information to a method.
209 """ Base class for decorators to add ArgumentParser information to a method.
210 """
210 """
211
211
212 def __call__(self, func):
212 def __call__(self, func):
213 if not getattr(func, 'has_arguments', False):
213 if not getattr(func, 'has_arguments', False):
214 func.has_arguments = True
214 func.has_arguments = True
215 func.decorators = []
215 func.decorators = []
216 func.decorators.append(self)
216 func.decorators.append(self)
217 return func
217 return func
218
218
219 def add_to_parser(self, parser, group):
219 def add_to_parser(self, parser, group):
220 """ Add this object's information to the parser, if necessary.
220 """ Add this object's information to the parser, if necessary.
221 """
221 """
222 pass
222 pass
223
223
224
224
225 class magic_arguments(ArgDecorator):
225 class magic_arguments(ArgDecorator):
226 """ Mark the magic as having argparse arguments and possibly adjust the
226 """ Mark the magic as having argparse arguments and possibly adjust the
227 name.
227 name.
228 """
228 """
229
229
230 def __init__(self, name=None):
230 def __init__(self, name=None):
231 self.name = name
231 self.name = name
232
232
233 def __call__(self, func):
233 def __call__(self, func):
234 if not getattr(func, 'has_arguments', False):
234 if not getattr(func, 'has_arguments', False):
235 func.has_arguments = True
235 func.has_arguments = True
236 func.decorators = []
236 func.decorators = []
237 if self.name is not None:
237 if self.name is not None:
238 func.argcmd_name = self.name
238 func.argcmd_name = self.name
239 # This should be the first decorator in the list of decorators, thus the
239 # This should be the first decorator in the list of decorators, thus the
240 # last to execute. Build the parser.
240 # last to execute. Build the parser.
241 func.parser = construct_parser(func)
241 func.parser = construct_parser(func)
242 return func
242 return func
243
243
244
244
245 class ArgMethodWrapper(ArgDecorator):
245 class ArgMethodWrapper(ArgDecorator):
246
246
247 """
247 """
248 Base class to define a wrapper for ArgumentParser method.
248 Base class to define a wrapper for ArgumentParser method.
249
249
250 Child class must define either `_method_name` or `add_to_parser`.
250 Child class must define either `_method_name` or `add_to_parser`.
251
251
252 """
252 """
253
253
254 _method_name = None
254 _method_name: str
255
255
256 def __init__(self, *args, **kwds):
256 def __init__(self, *args, **kwds):
257 self.args = args
257 self.args = args
258 self.kwds = kwds
258 self.kwds = kwds
259
259
260 def add_to_parser(self, parser, group):
260 def add_to_parser(self, parser, group):
261 """ Add this object's information to the parser.
261 """ Add this object's information to the parser.
262 """
262 """
263 if group is not None:
263 if group is not None:
264 parser = group
264 parser = group
265 getattr(parser, self._method_name)(*self.args, **self.kwds)
265 getattr(parser, self._method_name)(*self.args, **self.kwds)
266 return None
266 return None
267
267
268
268
269 class argument(ArgMethodWrapper):
269 class argument(ArgMethodWrapper):
270 """ Store arguments and keywords to pass to add_argument().
270 """ Store arguments and keywords to pass to add_argument().
271
271
272 Instances also serve to decorate command methods.
272 Instances also serve to decorate command methods.
273 """
273 """
274 _method_name = 'add_argument'
274 _method_name = 'add_argument'
275
275
276
276
277 class defaults(ArgMethodWrapper):
277 class defaults(ArgMethodWrapper):
278 """ Store arguments and keywords to pass to set_defaults().
278 """ Store arguments and keywords to pass to set_defaults().
279
279
280 Instances also serve to decorate command methods.
280 Instances also serve to decorate command methods.
281 """
281 """
282 _method_name = 'set_defaults'
282 _method_name = 'set_defaults'
283
283
284
284
285 class argument_group(ArgMethodWrapper):
285 class argument_group(ArgMethodWrapper):
286 """ Store arguments and keywords to pass to add_argument_group().
286 """ Store arguments and keywords to pass to add_argument_group().
287
287
288 Instances also serve to decorate command methods.
288 Instances also serve to decorate command methods.
289 """
289 """
290
290
291 def add_to_parser(self, parser, group):
291 def add_to_parser(self, parser, group):
292 """ Add this object's information to the parser.
292 """ Add this object's information to the parser.
293 """
293 """
294 return parser.add_argument_group(*self.args, **self.kwds)
294 return parser.add_argument_group(*self.args, **self.kwds)
295
295
296
296
297 class kwds(ArgDecorator):
297 class kwds(ArgDecorator):
298 """ Provide other keywords to the sub-parser constructor.
298 """ Provide other keywords to the sub-parser constructor.
299 """
299 """
300 def __init__(self, **kwds):
300 def __init__(self, **kwds):
301 self.kwds = kwds
301 self.kwds = kwds
302
302
303 def __call__(self, func):
303 def __call__(self, func):
304 func = super(kwds, self).__call__(func)
304 func = super(kwds, self).__call__(func)
305 func.argcmd_kwds = self.kwds
305 func.argcmd_kwds = self.kwds
306 return func
306 return func
307
307
308
308
309 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
309 __all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
310 'parse_argstring']
310 'parse_argstring']
@@ -1,1093 +1,1098 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 import ast
16 import ast
17 import inspect
17 import inspect
18 from inspect import signature
18 from inspect import signature
19 import html
19 import html
20 import linecache
20 import linecache
21 import warnings
21 import warnings
22 import os
22 import os
23 from textwrap import dedent
23 from textwrap import dedent
24 import types
24 import types
25 import io as stdlib_io
25 import io as stdlib_io
26
26
27 from typing import Union
27 from typing import Union
28
28
29 # IPython's own
29 # IPython's own
30 from IPython.core import page
30 from IPython.core import page
31 from IPython.lib.pretty import pretty
31 from IPython.lib.pretty import pretty
32 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.testing.skipdoctest import skip_doctest
33 from IPython.utils import PyColorize
33 from IPython.utils import PyColorize
34 from IPython.utils import openpy
34 from IPython.utils import openpy
35 from IPython.utils.dir2 import safe_hasattr
35 from IPython.utils.dir2 import safe_hasattr
36 from IPython.utils.path import compress_user
36 from IPython.utils.path import compress_user
37 from IPython.utils.text import indent
37 from IPython.utils.text import indent
38 from IPython.utils.wildcard import list_namespace
38 from IPython.utils.wildcard import list_namespace
39 from IPython.utils.wildcard import typestr2type
39 from IPython.utils.wildcard import typestr2type
40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
41 from IPython.utils.py3compat import cast_unicode
41 from IPython.utils.py3compat import cast_unicode
42 from IPython.utils.colorable import Colorable
42 from IPython.utils.colorable import Colorable
43 from IPython.utils.decorators import undoc
43 from IPython.utils.decorators import undoc
44
44
45 from pygments import highlight
45 from pygments import highlight
46 from pygments.lexers import PythonLexer
46 from pygments.lexers import PythonLexer
47 from pygments.formatters import HtmlFormatter
47 from pygments.formatters import HtmlFormatter
48
48
49 from typing import Any
49 from typing import Any, Optional
50 from dataclasses import dataclass
50 from dataclasses import dataclass
51
51
52
52
53 @dataclass
53 @dataclass
54 class OInfo:
54 class OInfo:
55 ismagic: bool
55 ismagic: bool
56 isalias: bool
56 isalias: bool
57 found: bool
57 found: bool
58 namespace: str
58 namespace: Optional[str]
59 parent: Any
59 parent: Any
60 obj: Any
60 obj: Any
61
61
62 def pylight(code):
62 def pylight(code):
63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
63 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
64
64
65 # builtin docstrings to ignore
65 # builtin docstrings to ignore
66 _func_call_docstring = types.FunctionType.__call__.__doc__
66 _func_call_docstring = types.FunctionType.__call__.__doc__
67 _object_init_docstring = object.__init__.__doc__
67 _object_init_docstring = object.__init__.__doc__
68 _builtin_type_docstrings = {
68 _builtin_type_docstrings = {
69 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
69 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
70 types.FunctionType, property)
70 types.FunctionType, property)
71 }
71 }
72
72
73 _builtin_func_type = type(all)
73 _builtin_func_type = type(all)
74 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
74 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
75 #****************************************************************************
75 #****************************************************************************
76 # Builtin color schemes
76 # Builtin color schemes
77
77
78 Colors = TermColors # just a shorthand
78 Colors = TermColors # just a shorthand
79
79
80 InspectColors = PyColorize.ANSICodeColors
80 InspectColors = PyColorize.ANSICodeColors
81
81
82 #****************************************************************************
82 #****************************************************************************
83 # Auxiliary functions and objects
83 # Auxiliary functions and objects
84
84
85 # See the messaging spec for the definition of all these fields. This list
85 # See the messaging spec for the definition of all these fields. This list
86 # effectively defines the order of display
86 # effectively defines the order of display
87 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
87 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
88 'length', 'file', 'definition', 'docstring', 'source',
88 'length', 'file', 'definition', 'docstring', 'source',
89 'init_definition', 'class_docstring', 'init_docstring',
89 'init_definition', 'class_docstring', 'init_docstring',
90 'call_def', 'call_docstring',
90 'call_def', 'call_docstring',
91 # These won't be printed but will be used to determine how to
91 # These won't be printed but will be used to determine how to
92 # format the object
92 # format the object
93 'ismagic', 'isalias', 'isclass', 'found', 'name'
93 'ismagic', 'isalias', 'isclass', 'found', 'name'
94 ]
94 ]
95
95
96
96
97 def object_info(**kw):
97 def object_info(**kw):
98 """Make an object info dict with all fields present."""
98 """Make an object info dict with all fields present."""
99 infodict = {k:None for k in info_fields}
99 infodict = {k:None for k in info_fields}
100 infodict.update(kw)
100 infodict.update(kw)
101 return infodict
101 return infodict
102
102
103
103
104 def get_encoding(obj):
104 def get_encoding(obj):
105 """Get encoding for python source file defining obj
105 """Get encoding for python source file defining obj
106
106
107 Returns None if obj is not defined in a sourcefile.
107 Returns None if obj is not defined in a sourcefile.
108 """
108 """
109 ofile = find_file(obj)
109 ofile = find_file(obj)
110 # run contents of file through pager starting at line where the object
110 # run contents of file through pager starting at line where the object
111 # is defined, as long as the file isn't binary and is actually on the
111 # is defined, as long as the file isn't binary and is actually on the
112 # filesystem.
112 # filesystem.
113 if ofile is None:
113 if ofile is None:
114 return None
114 return None
115 elif ofile.endswith(('.so', '.dll', '.pyd')):
115 elif ofile.endswith(('.so', '.dll', '.pyd')):
116 return None
116 return None
117 elif not os.path.isfile(ofile):
117 elif not os.path.isfile(ofile):
118 return None
118 return None
119 else:
119 else:
120 # Print only text files, not extension binaries. Note that
120 # Print only text files, not extension binaries. Note that
121 # getsourcelines returns lineno with 1-offset and page() uses
121 # getsourcelines returns lineno with 1-offset and page() uses
122 # 0-offset, so we must adjust.
122 # 0-offset, so we must adjust.
123 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
123 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
124 encoding, lines = openpy.detect_encoding(buffer.readline)
124 encoding, lines = openpy.detect_encoding(buffer.readline)
125 return encoding
125 return encoding
126
126
127 def getdoc(obj) -> Union[str,None]:
127 def getdoc(obj) -> Union[str,None]:
128 """Stable wrapper around inspect.getdoc.
128 """Stable wrapper around inspect.getdoc.
129
129
130 This can't crash because of attribute problems.
130 This can't crash because of attribute problems.
131
131
132 It also attempts to call a getdoc() method on the given object. This
132 It also attempts to call a getdoc() method on the given object. This
133 allows objects which provide their docstrings via non-standard mechanisms
133 allows objects which provide their docstrings via non-standard mechanisms
134 (like Pyro proxies) to still be inspected by ipython's ? system.
134 (like Pyro proxies) to still be inspected by ipython's ? system.
135 """
135 """
136 # Allow objects to offer customized documentation via a getdoc method:
136 # Allow objects to offer customized documentation via a getdoc method:
137 try:
137 try:
138 ds = obj.getdoc()
138 ds = obj.getdoc()
139 except Exception:
139 except Exception:
140 pass
140 pass
141 else:
141 else:
142 if isinstance(ds, str):
142 if isinstance(ds, str):
143 return inspect.cleandoc(ds)
143 return inspect.cleandoc(ds)
144 docstr = inspect.getdoc(obj)
144 docstr = inspect.getdoc(obj)
145 return docstr
145 return docstr
146
146
147
147
148 def getsource(obj, oname='') -> Union[str,None]:
148 def getsource(obj, oname='') -> Union[str,None]:
149 """Wrapper around inspect.getsource.
149 """Wrapper around inspect.getsource.
150
150
151 This can be modified by other projects to provide customized source
151 This can be modified by other projects to provide customized source
152 extraction.
152 extraction.
153
153
154 Parameters
154 Parameters
155 ----------
155 ----------
156 obj : object
156 obj : object
157 an object whose source code we will attempt to extract
157 an object whose source code we will attempt to extract
158 oname : str
158 oname : str
159 (optional) a name under which the object is known
159 (optional) a name under which the object is known
160
160
161 Returns
161 Returns
162 -------
162 -------
163 src : unicode or None
163 src : unicode or None
164
164
165 """
165 """
166
166
167 if isinstance(obj, property):
167 if isinstance(obj, property):
168 sources = []
168 sources = []
169 for attrname in ['fget', 'fset', 'fdel']:
169 for attrname in ['fget', 'fset', 'fdel']:
170 fn = getattr(obj, attrname)
170 fn = getattr(obj, attrname)
171 if fn is not None:
171 if fn is not None:
172 encoding = get_encoding(fn)
172 encoding = get_encoding(fn)
173 oname_prefix = ('%s.' % oname) if oname else ''
173 oname_prefix = ('%s.' % oname) if oname else ''
174 sources.append(''.join(('# ', oname_prefix, attrname)))
174 sources.append(''.join(('# ', oname_prefix, attrname)))
175 if inspect.isfunction(fn):
175 if inspect.isfunction(fn):
176 sources.append(dedent(getsource(fn)))
176 _src = getsource(fn)
177 if _src:
178 # assert _src is not None, "please mypy"
179 sources.append(dedent(_src))
177 else:
180 else:
178 # Default str/repr only prints function name,
181 # Default str/repr only prints function name,
179 # pretty.pretty prints module name too.
182 # pretty.pretty prints module name too.
180 sources.append(
183 sources.append(
181 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
184 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
182 )
185 )
183 if sources:
186 if sources:
184 return '\n'.join(sources)
187 return '\n'.join(sources)
185 else:
188 else:
186 return None
189 return None
187
190
188 else:
191 else:
189 # Get source for non-property objects.
192 # Get source for non-property objects.
190
193
191 obj = _get_wrapped(obj)
194 obj = _get_wrapped(obj)
192
195
193 try:
196 try:
194 src = inspect.getsource(obj)
197 src = inspect.getsource(obj)
195 except TypeError:
198 except TypeError:
196 # The object itself provided no meaningful source, try looking for
199 # The object itself provided no meaningful source, try looking for
197 # its class definition instead.
200 # its class definition instead.
198 try:
201 try:
199 src = inspect.getsource(obj.__class__)
202 src = inspect.getsource(obj.__class__)
200 except (OSError, TypeError):
203 except (OSError, TypeError):
201 return None
204 return None
202 except OSError:
205 except OSError:
203 return None
206 return None
204
207
205 return src
208 return src
206
209
207
210
208 def is_simple_callable(obj):
211 def is_simple_callable(obj):
209 """True if obj is a function ()"""
212 """True if obj is a function ()"""
210 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
213 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
211 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
214 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
212
215
213 @undoc
216 @undoc
214 def getargspec(obj):
217 def getargspec(obj):
215 """Wrapper around :func:`inspect.getfullargspec`
218 """Wrapper around :func:`inspect.getfullargspec`
216
219
217 In addition to functions and methods, this can also handle objects with a
220 In addition to functions and methods, this can also handle objects with a
218 ``__call__`` attribute.
221 ``__call__`` attribute.
219
222
220 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
223 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
221 """
224 """
222
225
223 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
226 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
224 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
227 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
225
228
226 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
229 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
227 obj = obj.__call__
230 obj = obj.__call__
228
231
229 return inspect.getfullargspec(obj)
232 return inspect.getfullargspec(obj)
230
233
231 @undoc
234 @undoc
232 def format_argspec(argspec):
235 def format_argspec(argspec):
233 """Format argspect, convenience wrapper around inspect's.
236 """Format argspect, convenience wrapper around inspect's.
234
237
235 This takes a dict instead of ordered arguments and calls
238 This takes a dict instead of ordered arguments and calls
236 inspect.format_argspec with the arguments in the necessary order.
239 inspect.format_argspec with the arguments in the necessary order.
237
240
238 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
241 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
239 """
242 """
240
243
241 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
244 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
242 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
245 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
243
246
244
247
245 return inspect.formatargspec(argspec['args'], argspec['varargs'],
248 return inspect.formatargspec(argspec['args'], argspec['varargs'],
246 argspec['varkw'], argspec['defaults'])
249 argspec['varkw'], argspec['defaults'])
247
250
248 @undoc
251 @undoc
249 def call_tip(oinfo, format_call=True):
252 def call_tip(oinfo, format_call=True):
250 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
253 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
251 warnings.warn(
254 warnings.warn(
252 "`call_tip` function is deprecated as of IPython 6.0"
255 "`call_tip` function is deprecated as of IPython 6.0"
253 "and will be removed in future versions.",
256 "and will be removed in future versions.",
254 DeprecationWarning,
257 DeprecationWarning,
255 stacklevel=2,
258 stacklevel=2,
256 )
259 )
257 # Get call definition
260 # Get call definition
258 argspec = oinfo.get('argspec')
261 argspec = oinfo.get('argspec')
259 if argspec is None:
262 if argspec is None:
260 call_line = None
263 call_line = None
261 else:
264 else:
262 # Callable objects will have 'self' as their first argument, prune
265 # Callable objects will have 'self' as their first argument, prune
263 # it out if it's there for clarity (since users do *not* pass an
266 # it out if it's there for clarity (since users do *not* pass an
264 # extra first argument explicitly).
267 # extra first argument explicitly).
265 try:
268 try:
266 has_self = argspec['args'][0] == 'self'
269 has_self = argspec['args'][0] == 'self'
267 except (KeyError, IndexError):
270 except (KeyError, IndexError):
268 pass
271 pass
269 else:
272 else:
270 if has_self:
273 if has_self:
271 argspec['args'] = argspec['args'][1:]
274 argspec['args'] = argspec['args'][1:]
272
275
273 call_line = oinfo['name']+format_argspec(argspec)
276 call_line = oinfo['name']+format_argspec(argspec)
274
277
275 # Now get docstring.
278 # Now get docstring.
276 # The priority is: call docstring, constructor docstring, main one.
279 # The priority is: call docstring, constructor docstring, main one.
277 doc = oinfo.get('call_docstring')
280 doc = oinfo.get('call_docstring')
278 if doc is None:
281 if doc is None:
279 doc = oinfo.get('init_docstring')
282 doc = oinfo.get('init_docstring')
280 if doc is None:
283 if doc is None:
281 doc = oinfo.get('docstring','')
284 doc = oinfo.get('docstring','')
282
285
283 return call_line, doc
286 return call_line, doc
284
287
285
288
286 def _get_wrapped(obj):
289 def _get_wrapped(obj):
287 """Get the original object if wrapped in one or more @decorators
290 """Get the original object if wrapped in one or more @decorators
288
291
289 Some objects automatically construct similar objects on any unrecognised
292 Some objects automatically construct similar objects on any unrecognised
290 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
293 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
291 this will arbitrarily cut off after 100 levels of obj.__wrapped__
294 this will arbitrarily cut off after 100 levels of obj.__wrapped__
292 attribute access. --TK, Jan 2016
295 attribute access. --TK, Jan 2016
293 """
296 """
294 orig_obj = obj
297 orig_obj = obj
295 i = 0
298 i = 0
296 while safe_hasattr(obj, '__wrapped__'):
299 while safe_hasattr(obj, '__wrapped__'):
297 obj = obj.__wrapped__
300 obj = obj.__wrapped__
298 i += 1
301 i += 1
299 if i > 100:
302 if i > 100:
300 # __wrapped__ is probably a lie, so return the thing we started with
303 # __wrapped__ is probably a lie, so return the thing we started with
301 return orig_obj
304 return orig_obj
302 return obj
305 return obj
303
306
304 def find_file(obj) -> str:
307 def find_file(obj) -> str:
305 """Find the absolute path to the file where an object was defined.
308 """Find the absolute path to the file where an object was defined.
306
309
307 This is essentially a robust wrapper around `inspect.getabsfile`.
310 This is essentially a robust wrapper around `inspect.getabsfile`.
308
311
309 Returns None if no file can be found.
312 Returns None if no file can be found.
310
313
311 Parameters
314 Parameters
312 ----------
315 ----------
313 obj : any Python object
316 obj : any Python object
314
317
315 Returns
318 Returns
316 -------
319 -------
317 fname : str
320 fname : str
318 The absolute path to the file where the object was defined.
321 The absolute path to the file where the object was defined.
319 """
322 """
320 obj = _get_wrapped(obj)
323 obj = _get_wrapped(obj)
321
324
322 fname = None
325 fname = None
323 try:
326 try:
324 fname = inspect.getabsfile(obj)
327 fname = inspect.getabsfile(obj)
325 except TypeError:
328 except TypeError:
326 # For an instance, the file that matters is where its class was
329 # For an instance, the file that matters is where its class was
327 # declared.
330 # declared.
328 try:
331 try:
329 fname = inspect.getabsfile(obj.__class__)
332 fname = inspect.getabsfile(obj.__class__)
330 except (OSError, TypeError):
333 except (OSError, TypeError):
331 # Can happen for builtins
334 # Can happen for builtins
332 pass
335 pass
333 except OSError:
336 except OSError:
334 pass
337 pass
335
338
336 return cast_unicode(fname)
339 return cast_unicode(fname)
337
340
338
341
339 def find_source_lines(obj):
342 def find_source_lines(obj):
340 """Find the line number in a file where an object was defined.
343 """Find the line number in a file where an object was defined.
341
344
342 This is essentially a robust wrapper around `inspect.getsourcelines`.
345 This is essentially a robust wrapper around `inspect.getsourcelines`.
343
346
344 Returns None if no file can be found.
347 Returns None if no file can be found.
345
348
346 Parameters
349 Parameters
347 ----------
350 ----------
348 obj : any Python object
351 obj : any Python object
349
352
350 Returns
353 Returns
351 -------
354 -------
352 lineno : int
355 lineno : int
353 The line number where the object definition starts.
356 The line number where the object definition starts.
354 """
357 """
355 obj = _get_wrapped(obj)
358 obj = _get_wrapped(obj)
356
359
357 try:
360 try:
358 lineno = inspect.getsourcelines(obj)[1]
361 lineno = inspect.getsourcelines(obj)[1]
359 except TypeError:
362 except TypeError:
360 # For instances, try the class object like getsource() does
363 # For instances, try the class object like getsource() does
361 try:
364 try:
362 lineno = inspect.getsourcelines(obj.__class__)[1]
365 lineno = inspect.getsourcelines(obj.__class__)[1]
363 except (OSError, TypeError):
366 except (OSError, TypeError):
364 return None
367 return None
365 except OSError:
368 except OSError:
366 return None
369 return None
367
370
368 return lineno
371 return lineno
369
372
370 class Inspector(Colorable):
373 class Inspector(Colorable):
371
374
372 def __init__(self, color_table=InspectColors,
375 def __init__(self, color_table=InspectColors,
373 code_color_table=PyColorize.ANSICodeColors,
376 code_color_table=PyColorize.ANSICodeColors,
374 scheme=None,
377 scheme=None,
375 str_detail_level=0,
378 str_detail_level=0,
376 parent=None, config=None):
379 parent=None, config=None):
377 super(Inspector, self).__init__(parent=parent, config=config)
380 super(Inspector, self).__init__(parent=parent, config=config)
378 self.color_table = color_table
381 self.color_table = color_table
379 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
382 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
380 self.format = self.parser.format
383 self.format = self.parser.format
381 self.str_detail_level = str_detail_level
384 self.str_detail_level = str_detail_level
382 self.set_active_scheme(scheme)
385 self.set_active_scheme(scheme)
383
386
384 def _getdef(self,obj,oname='') -> Union[str,None]:
387 def _getdef(self,obj,oname='') -> Union[str,None]:
385 """Return the call signature for any callable object.
388 """Return the call signature for any callable object.
386
389
387 If any exception is generated, None is returned instead and the
390 If any exception is generated, None is returned instead and the
388 exception is suppressed."""
391 exception is suppressed."""
389 try:
392 try:
390 return _render_signature(signature(obj), oname)
393 return _render_signature(signature(obj), oname)
391 except:
394 except:
392 return None
395 return None
393
396
394 def __head(self,h) -> str:
397 def __head(self,h) -> str:
395 """Return a header string with proper colors."""
398 """Return a header string with proper colors."""
396 return '%s%s%s' % (self.color_table.active_colors.header,h,
399 return '%s%s%s' % (self.color_table.active_colors.header,h,
397 self.color_table.active_colors.normal)
400 self.color_table.active_colors.normal)
398
401
399 def set_active_scheme(self, scheme):
402 def set_active_scheme(self, scheme):
400 if scheme is not None:
403 if scheme is not None:
401 self.color_table.set_active_scheme(scheme)
404 self.color_table.set_active_scheme(scheme)
402 self.parser.color_table.set_active_scheme(scheme)
405 self.parser.color_table.set_active_scheme(scheme)
403
406
404 def noinfo(self, msg, oname):
407 def noinfo(self, msg, oname):
405 """Generic message when no information is found."""
408 """Generic message when no information is found."""
406 print('No %s found' % msg, end=' ')
409 print('No %s found' % msg, end=' ')
407 if oname:
410 if oname:
408 print('for %s' % oname)
411 print('for %s' % oname)
409 else:
412 else:
410 print()
413 print()
411
414
412 def pdef(self, obj, oname=''):
415 def pdef(self, obj, oname=''):
413 """Print the call signature for any callable object.
416 """Print the call signature for any callable object.
414
417
415 If the object is a class, print the constructor information."""
418 If the object is a class, print the constructor information."""
416
419
417 if not callable(obj):
420 if not callable(obj):
418 print('Object is not callable.')
421 print('Object is not callable.')
419 return
422 return
420
423
421 header = ''
424 header = ''
422
425
423 if inspect.isclass(obj):
426 if inspect.isclass(obj):
424 header = self.__head('Class constructor information:\n')
427 header = self.__head('Class constructor information:\n')
425
428
426
429
427 output = self._getdef(obj,oname)
430 output = self._getdef(obj,oname)
428 if output is None:
431 if output is None:
429 self.noinfo('definition header',oname)
432 self.noinfo('definition header',oname)
430 else:
433 else:
431 print(header,self.format(output), end=' ')
434 print(header,self.format(output), end=' ')
432
435
433 # In Python 3, all classes are new-style, so they all have __init__.
436 # In Python 3, all classes are new-style, so they all have __init__.
434 @skip_doctest
437 @skip_doctest
435 def pdoc(self, obj, oname='', formatter=None):
438 def pdoc(self, obj, oname='', formatter=None):
436 """Print the docstring for any object.
439 """Print the docstring for any object.
437
440
438 Optional:
441 Optional:
439 -formatter: a function to run the docstring through for specially
442 -formatter: a function to run the docstring through for specially
440 formatted docstrings.
443 formatted docstrings.
441
444
442 Examples
445 Examples
443 --------
446 --------
444 In [1]: class NoInit:
447 In [1]: class NoInit:
445 ...: pass
448 ...: pass
446
449
447 In [2]: class NoDoc:
450 In [2]: class NoDoc:
448 ...: def __init__(self):
451 ...: def __init__(self):
449 ...: pass
452 ...: pass
450
453
451 In [3]: %pdoc NoDoc
454 In [3]: %pdoc NoDoc
452 No documentation found for NoDoc
455 No documentation found for NoDoc
453
456
454 In [4]: %pdoc NoInit
457 In [4]: %pdoc NoInit
455 No documentation found for NoInit
458 No documentation found for NoInit
456
459
457 In [5]: obj = NoInit()
460 In [5]: obj = NoInit()
458
461
459 In [6]: %pdoc obj
462 In [6]: %pdoc obj
460 No documentation found for obj
463 No documentation found for obj
461
464
462 In [5]: obj2 = NoDoc()
465 In [5]: obj2 = NoDoc()
463
466
464 In [6]: %pdoc obj2
467 In [6]: %pdoc obj2
465 No documentation found for obj2
468 No documentation found for obj2
466 """
469 """
467
470
468 head = self.__head # For convenience
471 head = self.__head # For convenience
469 lines = []
472 lines = []
470 ds = getdoc(obj)
473 ds = getdoc(obj)
471 if formatter:
474 if formatter:
472 ds = formatter(ds).get('plain/text', ds)
475 ds = formatter(ds).get('plain/text', ds)
473 if ds:
476 if ds:
474 lines.append(head("Class docstring:"))
477 lines.append(head("Class docstring:"))
475 lines.append(indent(ds))
478 lines.append(indent(ds))
476 if inspect.isclass(obj) and hasattr(obj, '__init__'):
479 if inspect.isclass(obj) and hasattr(obj, '__init__'):
477 init_ds = getdoc(obj.__init__)
480 init_ds = getdoc(obj.__init__)
478 if init_ds is not None:
481 if init_ds is not None:
479 lines.append(head("Init docstring:"))
482 lines.append(head("Init docstring:"))
480 lines.append(indent(init_ds))
483 lines.append(indent(init_ds))
481 elif hasattr(obj,'__call__'):
484 elif hasattr(obj,'__call__'):
482 call_ds = getdoc(obj.__call__)
485 call_ds = getdoc(obj.__call__)
483 if call_ds:
486 if call_ds:
484 lines.append(head("Call docstring:"))
487 lines.append(head("Call docstring:"))
485 lines.append(indent(call_ds))
488 lines.append(indent(call_ds))
486
489
487 if not lines:
490 if not lines:
488 self.noinfo('documentation',oname)
491 self.noinfo('documentation',oname)
489 else:
492 else:
490 page.page('\n'.join(lines))
493 page.page('\n'.join(lines))
491
494
492 def psource(self, obj, oname=''):
495 def psource(self, obj, oname=''):
493 """Print the source code for an object."""
496 """Print the source code for an object."""
494
497
495 # Flush the source cache because inspect can return out-of-date source
498 # Flush the source cache because inspect can return out-of-date source
496 linecache.checkcache()
499 linecache.checkcache()
497 try:
500 try:
498 src = getsource(obj, oname=oname)
501 src = getsource(obj, oname=oname)
499 except Exception:
502 except Exception:
500 src = None
503 src = None
501
504
502 if src is None:
505 if src is None:
503 self.noinfo('source', oname)
506 self.noinfo('source', oname)
504 else:
507 else:
505 page.page(self.format(src))
508 page.page(self.format(src))
506
509
507 def pfile(self, obj, oname=''):
510 def pfile(self, obj, oname=''):
508 """Show the whole file where an object was defined."""
511 """Show the whole file where an object was defined."""
509
512
510 lineno = find_source_lines(obj)
513 lineno = find_source_lines(obj)
511 if lineno is None:
514 if lineno is None:
512 self.noinfo('file', oname)
515 self.noinfo('file', oname)
513 return
516 return
514
517
515 ofile = find_file(obj)
518 ofile = find_file(obj)
516 # run contents of file through pager starting at line where the object
519 # run contents of file through pager starting at line where the object
517 # is defined, as long as the file isn't binary and is actually on the
520 # is defined, as long as the file isn't binary and is actually on the
518 # filesystem.
521 # filesystem.
519 if ofile.endswith(('.so', '.dll', '.pyd')):
522 if ofile.endswith(('.so', '.dll', '.pyd')):
520 print('File %r is binary, not printing.' % ofile)
523 print('File %r is binary, not printing.' % ofile)
521 elif not os.path.isfile(ofile):
524 elif not os.path.isfile(ofile):
522 print('File %r does not exist, not printing.' % ofile)
525 print('File %r does not exist, not printing.' % ofile)
523 else:
526 else:
524 # Print only text files, not extension binaries. Note that
527 # Print only text files, not extension binaries. Note that
525 # getsourcelines returns lineno with 1-offset and page() uses
528 # getsourcelines returns lineno with 1-offset and page() uses
526 # 0-offset, so we must adjust.
529 # 0-offset, so we must adjust.
527 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
530 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
528
531
529
532
530 def _mime_format(self, text:str, formatter=None) -> dict:
533 def _mime_format(self, text:str, formatter=None) -> dict:
531 """Return a mime bundle representation of the input text.
534 """Return a mime bundle representation of the input text.
532
535
533 - if `formatter` is None, the returned mime bundle has
536 - if `formatter` is None, the returned mime bundle has
534 a ``text/plain`` field, with the input text.
537 a ``text/plain`` field, with the input text.
535 a ``text/html`` field with a ``<pre>`` tag containing the input text.
538 a ``text/html`` field with a ``<pre>`` tag containing the input text.
536
539
537 - if ``formatter`` is not None, it must be a callable transforming the
540 - if ``formatter`` is not None, it must be a callable transforming the
538 input text into a mime bundle. Default values for ``text/plain`` and
541 input text into a mime bundle. Default values for ``text/plain`` and
539 ``text/html`` representations are the ones described above.
542 ``text/html`` representations are the ones described above.
540
543
541 Note:
544 Note:
542
545
543 Formatters returning strings are supported but this behavior is deprecated.
546 Formatters returning strings are supported but this behavior is deprecated.
544
547
545 """
548 """
546 defaults = {
549 defaults = {
547 "text/plain": text,
550 "text/plain": text,
548 "text/html": f"<pre>{html.escape(text)}</pre>",
551 "text/html": f"<pre>{html.escape(text)}</pre>",
549 }
552 }
550
553
551 if formatter is None:
554 if formatter is None:
552 return defaults
555 return defaults
553 else:
556 else:
554 formatted = formatter(text)
557 formatted = formatter(text)
555
558
556 if not isinstance(formatted, dict):
559 if not isinstance(formatted, dict):
557 # Handle the deprecated behavior of a formatter returning
560 # Handle the deprecated behavior of a formatter returning
558 # a string instead of a mime bundle.
561 # a string instead of a mime bundle.
559 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
562 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
560
563
561 else:
564 else:
562 return dict(defaults, **formatted)
565 return dict(defaults, **formatted)
563
566
564
567
565 def format_mime(self, bundle):
568 def format_mime(self, bundle):
566 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
569 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
567 # Format text/plain mimetype
570 # Format text/plain mimetype
568 if isinstance(bundle["text/plain"], (list, tuple)):
571 if isinstance(bundle["text/plain"], (list, tuple)):
569 # bundle['text/plain'] is a list of (head, formatted body) pairs
572 # bundle['text/plain'] is a list of (head, formatted body) pairs
570 lines = []
573 lines = []
571 _len = max(len(h) for h, _ in bundle["text/plain"])
574 _len = max(len(h) for h, _ in bundle["text/plain"])
572
575
573 for head, body in bundle["text/plain"]:
576 for head, body in bundle["text/plain"]:
574 body = body.strip("\n")
577 body = body.strip("\n")
575 delim = "\n" if "\n" in body else " "
578 delim = "\n" if "\n" in body else " "
576 lines.append(
579 lines.append(
577 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
580 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
578 )
581 )
579
582
580 bundle["text/plain"] = "\n".join(lines)
583 bundle["text/plain"] = "\n".join(lines)
581
584
582 # Format the text/html mimetype
585 # Format the text/html mimetype
583 if isinstance(bundle["text/html"], (list, tuple)):
586 if isinstance(bundle["text/html"], (list, tuple)):
584 # bundle['text/html'] is a list of (head, formatted body) pairs
587 # bundle['text/html'] is a list of (head, formatted body) pairs
585 bundle["text/html"] = "\n".join(
588 bundle["text/html"] = "\n".join(
586 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
589 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
587 )
590 )
588 return bundle
591 return bundle
589
592
590 def _append_info_field(
593 def _append_info_field(
591 self, bundle, title: str, key: str, info, omit_sections, formatter
594 self, bundle, title: str, key: str, info, omit_sections, formatter
592 ):
595 ):
593 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
596 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
594 if title in omit_sections or key in omit_sections:
597 if title in omit_sections or key in omit_sections:
595 return
598 return
596 field = info[key]
599 field = info[key]
597 if field is not None:
600 if field is not None:
598 formatted_field = self._mime_format(field, formatter)
601 formatted_field = self._mime_format(field, formatter)
599 bundle["text/plain"].append((title, formatted_field["text/plain"]))
602 bundle["text/plain"].append((title, formatted_field["text/plain"]))
600 bundle["text/html"].append((title, formatted_field["text/html"]))
603 bundle["text/html"].append((title, formatted_field["text/html"]))
601
604
602 def _make_info_unformatted(self, obj, info, formatter, detail_level, omit_sections):
605 def _make_info_unformatted(self, obj, info, formatter, detail_level, omit_sections):
603 """Assemble the mimebundle as unformatted lists of information"""
606 """Assemble the mimebundle as unformatted lists of information"""
604 bundle = {
607 bundle = {
605 "text/plain": [],
608 "text/plain": [],
606 "text/html": [],
609 "text/html": [],
607 }
610 }
608
611
609 # A convenience function to simplify calls below
612 # A convenience function to simplify calls below
610 def append_field(bundle, title: str, key: str, formatter=None):
613 def append_field(bundle, title: str, key: str, formatter=None):
611 self._append_info_field(
614 self._append_info_field(
612 bundle,
615 bundle,
613 title=title,
616 title=title,
614 key=key,
617 key=key,
615 info=info,
618 info=info,
616 omit_sections=omit_sections,
619 omit_sections=omit_sections,
617 formatter=formatter,
620 formatter=formatter,
618 )
621 )
619
622
620 def code_formatter(text):
623 def code_formatter(text):
621 return {
624 return {
622 'text/plain': self.format(text),
625 'text/plain': self.format(text),
623 'text/html': pylight(text)
626 'text/html': pylight(text)
624 }
627 }
625
628
626 if info["isalias"]:
629 if info["isalias"]:
627 append_field(bundle, "Repr", "string_form")
630 append_field(bundle, "Repr", "string_form")
628
631
629 elif info['ismagic']:
632 elif info['ismagic']:
630 if detail_level > 0:
633 if detail_level > 0:
631 append_field(bundle, "Source", "source", code_formatter)
634 append_field(bundle, "Source", "source", code_formatter)
632 else:
635 else:
633 append_field(bundle, "Docstring", "docstring", formatter)
636 append_field(bundle, "Docstring", "docstring", formatter)
634 append_field(bundle, "File", "file")
637 append_field(bundle, "File", "file")
635
638
636 elif info['isclass'] or is_simple_callable(obj):
639 elif info['isclass'] or is_simple_callable(obj):
637 # Functions, methods, classes
640 # Functions, methods, classes
638 append_field(bundle, "Signature", "definition", code_formatter)
641 append_field(bundle, "Signature", "definition", code_formatter)
639 append_field(bundle, "Init signature", "init_definition", code_formatter)
642 append_field(bundle, "Init signature", "init_definition", code_formatter)
640 append_field(bundle, "Docstring", "docstring", formatter)
643 append_field(bundle, "Docstring", "docstring", formatter)
641 if detail_level > 0 and info["source"]:
644 if detail_level > 0 and info["source"]:
642 append_field(bundle, "Source", "source", code_formatter)
645 append_field(bundle, "Source", "source", code_formatter)
643 else:
646 else:
644 append_field(bundle, "Init docstring", "init_docstring", formatter)
647 append_field(bundle, "Init docstring", "init_docstring", formatter)
645
648
646 append_field(bundle, "File", "file")
649 append_field(bundle, "File", "file")
647 append_field(bundle, "Type", "type_name")
650 append_field(bundle, "Type", "type_name")
648 append_field(bundle, "Subclasses", "subclasses")
651 append_field(bundle, "Subclasses", "subclasses")
649
652
650 else:
653 else:
651 # General Python objects
654 # General Python objects
652 append_field(bundle, "Signature", "definition", code_formatter)
655 append_field(bundle, "Signature", "definition", code_formatter)
653 append_field(bundle, "Call signature", "call_def", code_formatter)
656 append_field(bundle, "Call signature", "call_def", code_formatter)
654 append_field(bundle, "Type", "type_name")
657 append_field(bundle, "Type", "type_name")
655 append_field(bundle, "String form", "string_form")
658 append_field(bundle, "String form", "string_form")
656
659
657 # Namespace
660 # Namespace
658 if info["namespace"] != "Interactive":
661 if info["namespace"] != "Interactive":
659 append_field(bundle, "Namespace", "namespace")
662 append_field(bundle, "Namespace", "namespace")
660
663
661 append_field(bundle, "Length", "length")
664 append_field(bundle, "Length", "length")
662 append_field(bundle, "File", "file")
665 append_field(bundle, "File", "file")
663
666
664 # Source or docstring, depending on detail level and whether
667 # Source or docstring, depending on detail level and whether
665 # source found.
668 # source found.
666 if detail_level > 0 and info["source"]:
669 if detail_level > 0 and info["source"]:
667 append_field(bundle, "Source", "source", code_formatter)
670 append_field(bundle, "Source", "source", code_formatter)
668 else:
671 else:
669 append_field(bundle, "Docstring", "docstring", formatter)
672 append_field(bundle, "Docstring", "docstring", formatter)
670
673
671 append_field(bundle, "Class docstring", "class_docstring", formatter)
674 append_field(bundle, "Class docstring", "class_docstring", formatter)
672 append_field(bundle, "Init docstring", "init_docstring", formatter)
675 append_field(bundle, "Init docstring", "init_docstring", formatter)
673 append_field(bundle, "Call docstring", "call_docstring", formatter)
676 append_field(bundle, "Call docstring", "call_docstring", formatter)
674 return bundle
677 return bundle
675
678
676
679
677 def _get_info(
680 def _get_info(
678 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
681 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
679 ):
682 ):
680 """Retrieve an info dict and format it.
683 """Retrieve an info dict and format it.
681
684
682 Parameters
685 Parameters
683 ----------
686 ----------
684 obj : any
687 obj : any
685 Object to inspect and return info from
688 Object to inspect and return info from
686 oname : str (default: ''):
689 oname : str (default: ''):
687 Name of the variable pointing to `obj`.
690 Name of the variable pointing to `obj`.
688 formatter : callable
691 formatter : callable
689 info
692 info
690 already computed information
693 already computed information
691 detail_level : integer
694 detail_level : integer
692 Granularity of detail level, if set to 1, give more information.
695 Granularity of detail level, if set to 1, give more information.
693 omit_sections : container[str]
696 omit_sections : container[str]
694 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
697 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
695 """
698 """
696
699
697 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
700 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
698 bundle = self._make_info_unformatted(
701 bundle = self._make_info_unformatted(
699 obj, info, formatter, detail_level=detail_level, omit_sections=omit_sections
702 obj, info, formatter, detail_level=detail_level, omit_sections=omit_sections
700 )
703 )
701 return self.format_mime(bundle)
704 return self.format_mime(bundle)
702
705
703 def pinfo(
706 def pinfo(
704 self,
707 self,
705 obj,
708 obj,
706 oname="",
709 oname="",
707 formatter=None,
710 formatter=None,
708 info=None,
711 info=None,
709 detail_level=0,
712 detail_level=0,
710 enable_html_pager=True,
713 enable_html_pager=True,
711 omit_sections=(),
714 omit_sections=(),
712 ):
715 ):
713 """Show detailed information about an object.
716 """Show detailed information about an object.
714
717
715 Optional arguments:
718 Optional arguments:
716
719
717 - oname: name of the variable pointing to the object.
720 - oname: name of the variable pointing to the object.
718
721
719 - formatter: callable (optional)
722 - formatter: callable (optional)
720 A special formatter for docstrings.
723 A special formatter for docstrings.
721
724
722 The formatter is a callable that takes a string as an input
725 The formatter is a callable that takes a string as an input
723 and returns either a formatted string or a mime type bundle
726 and returns either a formatted string or a mime type bundle
724 in the form of a dictionary.
727 in the form of a dictionary.
725
728
726 Although the support of custom formatter returning a string
729 Although the support of custom formatter returning a string
727 instead of a mime type bundle is deprecated.
730 instead of a mime type bundle is deprecated.
728
731
729 - info: a structure with some information fields which may have been
732 - info: a structure with some information fields which may have been
730 precomputed already.
733 precomputed already.
731
734
732 - detail_level: if set to 1, more information is given.
735 - detail_level: if set to 1, more information is given.
733
736
734 - omit_sections: set of section keys and titles to omit
737 - omit_sections: set of section keys and titles to omit
735 """
738 """
736 info = self._get_info(
739 info = self._get_info(
737 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
740 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
738 )
741 )
739 if not enable_html_pager:
742 if not enable_html_pager:
740 del info['text/html']
743 del info['text/html']
741 page.page(info)
744 page.page(info)
742
745
743 def _info(self, obj, oname="", info=None, detail_level=0):
746 def _info(self, obj, oname="", info=None, detail_level=0):
744 """
747 """
745 Inspector.info() was likely improperly marked as deprecated
748 Inspector.info() was likely improperly marked as deprecated
746 while only a parameter was deprecated. We "un-deprecate" it.
749 while only a parameter was deprecated. We "un-deprecate" it.
747 """
750 """
748
751
749 warnings.warn(
752 warnings.warn(
750 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
753 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
751 "and the `formatter=` keyword removed. `Inspector._info` is now "
754 "and the `formatter=` keyword removed. `Inspector._info` is now "
752 "an alias, and you can just call `.info()` directly.",
755 "an alias, and you can just call `.info()` directly.",
753 DeprecationWarning,
756 DeprecationWarning,
754 stacklevel=2,
757 stacklevel=2,
755 )
758 )
756 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
759 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
757
760
758 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
761 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
759 """Compute a dict with detailed information about an object.
762 """Compute a dict with detailed information about an object.
760
763
761 Parameters
764 Parameters
762 ----------
765 ----------
763 obj : any
766 obj : any
764 An object to find information about
767 An object to find information about
765 oname : str (default: '')
768 oname : str (default: '')
766 Name of the variable pointing to `obj`.
769 Name of the variable pointing to `obj`.
767 info : (default: None)
770 info : (default: None)
768 A struct (dict like with attr access) with some information fields
771 A struct (dict like with attr access) with some information fields
769 which may have been precomputed already.
772 which may have been precomputed already.
770 detail_level : int (default:0)
773 detail_level : int (default:0)
771 If set to 1, more information is given.
774 If set to 1, more information is given.
772
775
773 Returns
776 Returns
774 -------
777 -------
775 An object info dict with known fields from `info_fields`. Keys are
778 An object info dict with known fields from `info_fields`. Keys are
776 strings, values are string or None.
779 strings, values are string or None.
777 """
780 """
778
781
779 if info is None:
782 if info is None:
780 ismagic = False
783 ismagic = False
781 isalias = False
784 isalias = False
782 ospace = ''
785 ospace = ''
783 else:
786 else:
784 ismagic = info.ismagic
787 ismagic = info.ismagic
785 isalias = info.isalias
788 isalias = info.isalias
786 ospace = info.namespace
789 ospace = info.namespace
787
790
788 # Get docstring, special-casing aliases:
791 # Get docstring, special-casing aliases:
789 if isalias:
792 if isalias:
790 if not callable(obj):
793 if not callable(obj):
791 try:
794 try:
792 ds = "Alias to the system command:\n %s" % obj[1]
795 ds = "Alias to the system command:\n %s" % obj[1]
793 except:
796 except:
794 ds = "Alias: " + str(obj)
797 ds = "Alias: " + str(obj)
795 else:
798 else:
796 ds = "Alias to " + str(obj)
799 ds = "Alias to " + str(obj)
797 if obj.__doc__:
800 if obj.__doc__:
798 ds += "\nDocstring:\n" + obj.__doc__
801 ds += "\nDocstring:\n" + obj.__doc__
799 else:
802 else:
800 ds = getdoc(obj)
803 ds_or_None = getdoc(obj)
801 if ds is None:
804 if ds_or_None is None:
802 ds = '<no docstring>'
805 ds = '<no docstring>'
806 else:
807 ds = ds_or_None
803
808
804 # store output in a dict, we initialize it here and fill it as we go
809 # store output in a dict, we initialize it here and fill it as we go
805 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
810 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
806
811
807 string_max = 200 # max size of strings to show (snipped if longer)
812 string_max = 200 # max size of strings to show (snipped if longer)
808 shalf = int((string_max - 5) / 2)
813 shalf = int((string_max - 5) / 2)
809
814
810 if ismagic:
815 if ismagic:
811 out['type_name'] = 'Magic function'
816 out['type_name'] = 'Magic function'
812 elif isalias:
817 elif isalias:
813 out['type_name'] = 'System alias'
818 out['type_name'] = 'System alias'
814 else:
819 else:
815 out['type_name'] = type(obj).__name__
820 out['type_name'] = type(obj).__name__
816
821
817 try:
822 try:
818 bclass = obj.__class__
823 bclass = obj.__class__
819 out['base_class'] = str(bclass)
824 out['base_class'] = str(bclass)
820 except:
825 except:
821 pass
826 pass
822
827
823 # String form, but snip if too long in ? form (full in ??)
828 # String form, but snip if too long in ? form (full in ??)
824 if detail_level >= self.str_detail_level:
829 if detail_level >= self.str_detail_level:
825 try:
830 try:
826 ostr = str(obj)
831 ostr = str(obj)
827 str_head = 'string_form'
832 str_head = 'string_form'
828 if not detail_level and len(ostr)>string_max:
833 if not detail_level and len(ostr)>string_max:
829 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
834 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
830 ostr = ("\n" + " " * len(str_head.expandtabs())).\
835 ostr = ("\n" + " " * len(str_head.expandtabs())).\
831 join(q.strip() for q in ostr.split("\n"))
836 join(q.strip() for q in ostr.split("\n"))
832 out[str_head] = ostr
837 out[str_head] = ostr
833 except:
838 except:
834 pass
839 pass
835
840
836 if ospace:
841 if ospace:
837 out['namespace'] = ospace
842 out['namespace'] = ospace
838
843
839 # Length (for strings and lists)
844 # Length (for strings and lists)
840 try:
845 try:
841 out['length'] = str(len(obj))
846 out['length'] = str(len(obj))
842 except Exception:
847 except Exception:
843 pass
848 pass
844
849
845 # Filename where object was defined
850 # Filename where object was defined
846 binary_file = False
851 binary_file = False
847 fname = find_file(obj)
852 fname = find_file(obj)
848 if fname is None:
853 if fname is None:
849 # if anything goes wrong, we don't want to show source, so it's as
854 # if anything goes wrong, we don't want to show source, so it's as
850 # if the file was binary
855 # if the file was binary
851 binary_file = True
856 binary_file = True
852 else:
857 else:
853 if fname.endswith(('.so', '.dll', '.pyd')):
858 if fname.endswith(('.so', '.dll', '.pyd')):
854 binary_file = True
859 binary_file = True
855 elif fname.endswith('<string>'):
860 elif fname.endswith('<string>'):
856 fname = 'Dynamically generated function. No source code available.'
861 fname = 'Dynamically generated function. No source code available.'
857 out['file'] = compress_user(fname)
862 out['file'] = compress_user(fname)
858
863
859 # Original source code for a callable, class or property.
864 # Original source code for a callable, class or property.
860 if detail_level:
865 if detail_level:
861 # Flush the source cache because inspect can return out-of-date
866 # Flush the source cache because inspect can return out-of-date
862 # source
867 # source
863 linecache.checkcache()
868 linecache.checkcache()
864 try:
869 try:
865 if isinstance(obj, property) or not binary_file:
870 if isinstance(obj, property) or not binary_file:
866 src = getsource(obj, oname)
871 src = getsource(obj, oname)
867 if src is not None:
872 if src is not None:
868 src = src.rstrip()
873 src = src.rstrip()
869 out['source'] = src
874 out['source'] = src
870
875
871 except Exception:
876 except Exception:
872 pass
877 pass
873
878
874 # Add docstring only if no source is to be shown (avoid repetitions).
879 # Add docstring only if no source is to be shown (avoid repetitions).
875 if ds and not self._source_contains_docstring(out.get('source'), ds):
880 if ds and not self._source_contains_docstring(out.get('source'), ds):
876 out['docstring'] = ds
881 out['docstring'] = ds
877
882
878 # Constructor docstring for classes
883 # Constructor docstring for classes
879 if inspect.isclass(obj):
884 if inspect.isclass(obj):
880 out['isclass'] = True
885 out['isclass'] = True
881
886
882 # get the init signature:
887 # get the init signature:
883 try:
888 try:
884 init_def = self._getdef(obj, oname)
889 init_def = self._getdef(obj, oname)
885 except AttributeError:
890 except AttributeError:
886 init_def = None
891 init_def = None
887
892
888 # get the __init__ docstring
893 # get the __init__ docstring
889 try:
894 try:
890 obj_init = obj.__init__
895 obj_init = obj.__init__
891 except AttributeError:
896 except AttributeError:
892 init_ds = None
897 init_ds = None
893 else:
898 else:
894 if init_def is None:
899 if init_def is None:
895 # Get signature from init if top-level sig failed.
900 # Get signature from init if top-level sig failed.
896 # Can happen for built-in types (list, etc.).
901 # Can happen for built-in types (list, etc.).
897 try:
902 try:
898 init_def = self._getdef(obj_init, oname)
903 init_def = self._getdef(obj_init, oname)
899 except AttributeError:
904 except AttributeError:
900 pass
905 pass
901 init_ds = getdoc(obj_init)
906 init_ds = getdoc(obj_init)
902 # Skip Python's auto-generated docstrings
907 # Skip Python's auto-generated docstrings
903 if init_ds == _object_init_docstring:
908 if init_ds == _object_init_docstring:
904 init_ds = None
909 init_ds = None
905
910
906 if init_def:
911 if init_def:
907 out['init_definition'] = init_def
912 out['init_definition'] = init_def
908
913
909 if init_ds:
914 if init_ds:
910 out['init_docstring'] = init_ds
915 out['init_docstring'] = init_ds
911
916
912 names = [sub.__name__ for sub in type.__subclasses__(obj)]
917 names = [sub.__name__ for sub in type.__subclasses__(obj)]
913 if len(names) < 10:
918 if len(names) < 10:
914 all_names = ', '.join(names)
919 all_names = ', '.join(names)
915 else:
920 else:
916 all_names = ', '.join(names[:10]+['...'])
921 all_names = ', '.join(names[:10]+['...'])
917 out['subclasses'] = all_names
922 out['subclasses'] = all_names
918 # and class docstring for instances:
923 # and class docstring for instances:
919 else:
924 else:
920 # reconstruct the function definition and print it:
925 # reconstruct the function definition and print it:
921 defln = self._getdef(obj, oname)
926 defln = self._getdef(obj, oname)
922 if defln:
927 if defln:
923 out['definition'] = defln
928 out['definition'] = defln
924
929
925 # First, check whether the instance docstring is identical to the
930 # First, check whether the instance docstring is identical to the
926 # class one, and print it separately if they don't coincide. In
931 # class one, and print it separately if they don't coincide. In
927 # most cases they will, but it's nice to print all the info for
932 # most cases they will, but it's nice to print all the info for
928 # objects which use instance-customized docstrings.
933 # objects which use instance-customized docstrings.
929 if ds:
934 if ds:
930 try:
935 try:
931 cls = getattr(obj,'__class__')
936 cls = getattr(obj,'__class__')
932 except:
937 except:
933 class_ds = None
938 class_ds = None
934 else:
939 else:
935 class_ds = getdoc(cls)
940 class_ds = getdoc(cls)
936 # Skip Python's auto-generated docstrings
941 # Skip Python's auto-generated docstrings
937 if class_ds in _builtin_type_docstrings:
942 if class_ds in _builtin_type_docstrings:
938 class_ds = None
943 class_ds = None
939 if class_ds and ds != class_ds:
944 if class_ds and ds != class_ds:
940 out['class_docstring'] = class_ds
945 out['class_docstring'] = class_ds
941
946
942 # Next, try to show constructor docstrings
947 # Next, try to show constructor docstrings
943 try:
948 try:
944 init_ds = getdoc(obj.__init__)
949 init_ds = getdoc(obj.__init__)
945 # Skip Python's auto-generated docstrings
950 # Skip Python's auto-generated docstrings
946 if init_ds == _object_init_docstring:
951 if init_ds == _object_init_docstring:
947 init_ds = None
952 init_ds = None
948 except AttributeError:
953 except AttributeError:
949 init_ds = None
954 init_ds = None
950 if init_ds:
955 if init_ds:
951 out['init_docstring'] = init_ds
956 out['init_docstring'] = init_ds
952
957
953 # Call form docstring for callable instances
958 # Call form docstring for callable instances
954 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
959 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
955 call_def = self._getdef(obj.__call__, oname)
960 call_def = self._getdef(obj.__call__, oname)
956 if call_def and (call_def != out.get('definition')):
961 if call_def and (call_def != out.get('definition')):
957 # it may never be the case that call def and definition differ,
962 # it may never be the case that call def and definition differ,
958 # but don't include the same signature twice
963 # but don't include the same signature twice
959 out['call_def'] = call_def
964 out['call_def'] = call_def
960 call_ds = getdoc(obj.__call__)
965 call_ds = getdoc(obj.__call__)
961 # Skip Python's auto-generated docstrings
966 # Skip Python's auto-generated docstrings
962 if call_ds == _func_call_docstring:
967 if call_ds == _func_call_docstring:
963 call_ds = None
968 call_ds = None
964 if call_ds:
969 if call_ds:
965 out['call_docstring'] = call_ds
970 out['call_docstring'] = call_ds
966
971
967 return object_info(**out)
972 return object_info(**out)
968
973
969 @staticmethod
974 @staticmethod
970 def _source_contains_docstring(src, doc):
975 def _source_contains_docstring(src, doc):
971 """
976 """
972 Check whether the source *src* contains the docstring *doc*.
977 Check whether the source *src* contains the docstring *doc*.
973
978
974 This is is helper function to skip displaying the docstring if the
979 This is is helper function to skip displaying the docstring if the
975 source already contains it, avoiding repetition of information.
980 source already contains it, avoiding repetition of information.
976 """
981 """
977 try:
982 try:
978 def_node, = ast.parse(dedent(src)).body
983 def_node, = ast.parse(dedent(src)).body
979 return ast.get_docstring(def_node) == doc
984 return ast.get_docstring(def_node) == doc
980 except Exception:
985 except Exception:
981 # The source can become invalid or even non-existent (because it
986 # The source can become invalid or even non-existent (because it
982 # is re-fetched from the source file) so the above code fail in
987 # is re-fetched from the source file) so the above code fail in
983 # arbitrary ways.
988 # arbitrary ways.
984 return False
989 return False
985
990
986 def psearch(self,pattern,ns_table,ns_search=[],
991 def psearch(self,pattern,ns_table,ns_search=[],
987 ignore_case=False,show_all=False, *, list_types=False):
992 ignore_case=False,show_all=False, *, list_types=False):
988 """Search namespaces with wildcards for objects.
993 """Search namespaces with wildcards for objects.
989
994
990 Arguments:
995 Arguments:
991
996
992 - pattern: string containing shell-like wildcards to use in namespace
997 - pattern: string containing shell-like wildcards to use in namespace
993 searches and optionally a type specification to narrow the search to
998 searches and optionally a type specification to narrow the search to
994 objects of that type.
999 objects of that type.
995
1000
996 - ns_table: dict of name->namespaces for search.
1001 - ns_table: dict of name->namespaces for search.
997
1002
998 Optional arguments:
1003 Optional arguments:
999
1004
1000 - ns_search: list of namespace names to include in search.
1005 - ns_search: list of namespace names to include in search.
1001
1006
1002 - ignore_case(False): make the search case-insensitive.
1007 - ignore_case(False): make the search case-insensitive.
1003
1008
1004 - show_all(False): show all names, including those starting with
1009 - show_all(False): show all names, including those starting with
1005 underscores.
1010 underscores.
1006
1011
1007 - list_types(False): list all available object types for object matching.
1012 - list_types(False): list all available object types for object matching.
1008 """
1013 """
1009 #print 'ps pattern:<%r>' % pattern # dbg
1014 #print 'ps pattern:<%r>' % pattern # dbg
1010
1015
1011 # defaults
1016 # defaults
1012 type_pattern = 'all'
1017 type_pattern = 'all'
1013 filter = ''
1018 filter = ''
1014
1019
1015 # list all object types
1020 # list all object types
1016 if list_types:
1021 if list_types:
1017 page.page('\n'.join(sorted(typestr2type)))
1022 page.page('\n'.join(sorted(typestr2type)))
1018 return
1023 return
1019
1024
1020 cmds = pattern.split()
1025 cmds = pattern.split()
1021 len_cmds = len(cmds)
1026 len_cmds = len(cmds)
1022 if len_cmds == 1:
1027 if len_cmds == 1:
1023 # Only filter pattern given
1028 # Only filter pattern given
1024 filter = cmds[0]
1029 filter = cmds[0]
1025 elif len_cmds == 2:
1030 elif len_cmds == 2:
1026 # Both filter and type specified
1031 # Both filter and type specified
1027 filter,type_pattern = cmds
1032 filter,type_pattern = cmds
1028 else:
1033 else:
1029 raise ValueError('invalid argument string for psearch: <%s>' %
1034 raise ValueError('invalid argument string for psearch: <%s>' %
1030 pattern)
1035 pattern)
1031
1036
1032 # filter search namespaces
1037 # filter search namespaces
1033 for name in ns_search:
1038 for name in ns_search:
1034 if name not in ns_table:
1039 if name not in ns_table:
1035 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1040 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1036 (name,ns_table.keys()))
1041 (name,ns_table.keys()))
1037
1042
1038 #print 'type_pattern:',type_pattern # dbg
1043 #print 'type_pattern:',type_pattern # dbg
1039 search_result, namespaces_seen = set(), set()
1044 search_result, namespaces_seen = set(), set()
1040 for ns_name in ns_search:
1045 for ns_name in ns_search:
1041 ns = ns_table[ns_name]
1046 ns = ns_table[ns_name]
1042 # Normally, locals and globals are the same, so we just check one.
1047 # Normally, locals and globals are the same, so we just check one.
1043 if id(ns) in namespaces_seen:
1048 if id(ns) in namespaces_seen:
1044 continue
1049 continue
1045 namespaces_seen.add(id(ns))
1050 namespaces_seen.add(id(ns))
1046 tmp_res = list_namespace(ns, type_pattern, filter,
1051 tmp_res = list_namespace(ns, type_pattern, filter,
1047 ignore_case=ignore_case, show_all=show_all)
1052 ignore_case=ignore_case, show_all=show_all)
1048 search_result.update(tmp_res)
1053 search_result.update(tmp_res)
1049
1054
1050 page.page('\n'.join(sorted(search_result)))
1055 page.page('\n'.join(sorted(search_result)))
1051
1056
1052
1057
1053 def _render_signature(obj_signature, obj_name) -> str:
1058 def _render_signature(obj_signature, obj_name) -> str:
1054 """
1059 """
1055 This was mostly taken from inspect.Signature.__str__.
1060 This was mostly taken from inspect.Signature.__str__.
1056 Look there for the comments.
1061 Look there for the comments.
1057 The only change is to add linebreaks when this gets too long.
1062 The only change is to add linebreaks when this gets too long.
1058 """
1063 """
1059 result = []
1064 result = []
1060 pos_only = False
1065 pos_only = False
1061 kw_only = True
1066 kw_only = True
1062 for param in obj_signature.parameters.values():
1067 for param in obj_signature.parameters.values():
1063 if param.kind == inspect._POSITIONAL_ONLY:
1068 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1064 pos_only = True
1069 pos_only = True
1065 elif pos_only:
1070 elif pos_only:
1066 result.append('/')
1071 result.append('/')
1067 pos_only = False
1072 pos_only = False
1068
1073
1069 if param.kind == inspect._VAR_POSITIONAL:
1074 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1070 kw_only = False
1075 kw_only = False
1071 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1076 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1072 result.append('*')
1077 result.append('*')
1073 kw_only = False
1078 kw_only = False
1074
1079
1075 result.append(str(param))
1080 result.append(str(param))
1076
1081
1077 if pos_only:
1082 if pos_only:
1078 result.append('/')
1083 result.append('/')
1079
1084
1080 # add up name, parameters, braces (2), and commas
1085 # add up name, parameters, braces (2), and commas
1081 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1086 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1082 # This doesn’t fit behind “Signature: ” in an inspect window.
1087 # This doesn’t fit behind “Signature: ” in an inspect window.
1083 rendered = '{}(\n{})'.format(obj_name, ''.join(
1088 rendered = '{}(\n{})'.format(obj_name, ''.join(
1084 ' {},\n'.format(r) for r in result)
1089 ' {},\n'.format(r) for r in result)
1085 )
1090 )
1086 else:
1091 else:
1087 rendered = '{}({})'.format(obj_name, ', '.join(result))
1092 rendered = '{}({})'.format(obj_name, ', '.join(result))
1088
1093
1089 if obj_signature.return_annotation is not inspect._empty:
1094 if obj_signature.return_annotation is not inspect._empty:
1090 anno = inspect.formatannotation(obj_signature.return_annotation)
1095 anno = inspect.formatannotation(obj_signature.return_annotation)
1091 rendered += ' -> {}'.format(anno)
1096 rendered += ' -> {}'.format(anno)
1092
1097
1093 return rendered
1098 return rendered
@@ -1,1200 +1,1200 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 import pytest
20 import pytest
21 from unittest import mock
21 from unittest import mock
22
22
23 from os.path import join
23 from os.path import join
24
24
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 from IPython.core.oinspect import OInfo
29 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
31 )
31 )
32 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
33 from IPython.utils.process import find_cmd
33 from IPython.utils.process import find_cmd
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Globals
36 # Globals
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # This is used by every single test, no point repeating it ad nauseam
38 # This is used by every single test, no point repeating it ad nauseam
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Tests
41 # Tests
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 class DerivedInterrupt(KeyboardInterrupt):
44 class DerivedInterrupt(KeyboardInterrupt):
45 pass
45 pass
46
46
47 class InteractiveShellTestCase(unittest.TestCase):
47 class InteractiveShellTestCase(unittest.TestCase):
48 def test_naked_string_cells(self):
48 def test_naked_string_cells(self):
49 """Test that cells with only naked strings are fully executed"""
49 """Test that cells with only naked strings are fully executed"""
50 # First, single-line inputs
50 # First, single-line inputs
51 ip.run_cell('"a"\n')
51 ip.run_cell('"a"\n')
52 self.assertEqual(ip.user_ns['_'], 'a')
52 self.assertEqual(ip.user_ns['_'], 'a')
53 # And also multi-line cells
53 # And also multi-line cells
54 ip.run_cell('"""a\nb"""\n')
54 ip.run_cell('"""a\nb"""\n')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
56
56
57 def test_run_empty_cell(self):
57 def test_run_empty_cell(self):
58 """Just make sure we don't get a horrible error with a blank
58 """Just make sure we don't get a horrible error with a blank
59 cell of input. Yes, I did overlook that."""
59 cell of input. Yes, I did overlook that."""
60 old_xc = ip.execution_count
60 old_xc = ip.execution_count
61 res = ip.run_cell('')
61 res = ip.run_cell('')
62 self.assertEqual(ip.execution_count, old_xc)
62 self.assertEqual(ip.execution_count, old_xc)
63 self.assertEqual(res.execution_count, None)
63 self.assertEqual(res.execution_count, None)
64
64
65 def test_run_cell_multiline(self):
65 def test_run_cell_multiline(self):
66 """Multi-block, multi-line cells must execute correctly.
66 """Multi-block, multi-line cells must execute correctly.
67 """
67 """
68 src = '\n'.join(["x=1",
68 src = '\n'.join(["x=1",
69 "y=2",
69 "y=2",
70 "if 1:",
70 "if 1:",
71 " x += 1",
71 " x += 1",
72 " y += 1",])
72 " y += 1",])
73 res = ip.run_cell(src)
73 res = ip.run_cell(src)
74 self.assertEqual(ip.user_ns['x'], 2)
74 self.assertEqual(ip.user_ns['x'], 2)
75 self.assertEqual(ip.user_ns['y'], 3)
75 self.assertEqual(ip.user_ns['y'], 3)
76 self.assertEqual(res.success, True)
76 self.assertEqual(res.success, True)
77 self.assertEqual(res.result, None)
77 self.assertEqual(res.result, None)
78
78
79 def test_multiline_string_cells(self):
79 def test_multiline_string_cells(self):
80 "Code sprinkled with multiline strings should execute (GH-306)"
80 "Code sprinkled with multiline strings should execute (GH-306)"
81 ip.run_cell('tmp=0')
81 ip.run_cell('tmp=0')
82 self.assertEqual(ip.user_ns['tmp'], 0)
82 self.assertEqual(ip.user_ns['tmp'], 0)
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
84 self.assertEqual(ip.user_ns['tmp'], 1)
84 self.assertEqual(ip.user_ns['tmp'], 1)
85 self.assertEqual(res.success, True)
85 self.assertEqual(res.success, True)
86 self.assertEqual(res.result, "a\nb")
86 self.assertEqual(res.result, "a\nb")
87
87
88 def test_dont_cache_with_semicolon(self):
88 def test_dont_cache_with_semicolon(self):
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
90 oldlen = len(ip.user_ns['Out'])
90 oldlen = len(ip.user_ns['Out'])
91 for cell in ['1;', '1;1;']:
91 for cell in ['1;', '1;1;']:
92 res = ip.run_cell(cell, store_history=True)
92 res = ip.run_cell(cell, store_history=True)
93 newlen = len(ip.user_ns['Out'])
93 newlen = len(ip.user_ns['Out'])
94 self.assertEqual(oldlen, newlen)
94 self.assertEqual(oldlen, newlen)
95 self.assertIsNone(res.result)
95 self.assertIsNone(res.result)
96 i = 0
96 i = 0
97 #also test the default caching behavior
97 #also test the default caching behavior
98 for cell in ['1', '1;1']:
98 for cell in ['1', '1;1']:
99 ip.run_cell(cell, store_history=True)
99 ip.run_cell(cell, store_history=True)
100 newlen = len(ip.user_ns['Out'])
100 newlen = len(ip.user_ns['Out'])
101 i += 1
101 i += 1
102 self.assertEqual(oldlen+i, newlen)
102 self.assertEqual(oldlen+i, newlen)
103
103
104 def test_syntax_error(self):
104 def test_syntax_error(self):
105 res = ip.run_cell("raise = 3")
105 res = ip.run_cell("raise = 3")
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
107
107
108 def test_open_standard_input_stream(self):
108 def test_open_standard_input_stream(self):
109 res = ip.run_cell("open(0)")
109 res = ip.run_cell("open(0)")
110 self.assertIsInstance(res.error_in_exec, ValueError)
110 self.assertIsInstance(res.error_in_exec, ValueError)
111
111
112 def test_open_standard_output_stream(self):
112 def test_open_standard_output_stream(self):
113 res = ip.run_cell("open(1)")
113 res = ip.run_cell("open(1)")
114 self.assertIsInstance(res.error_in_exec, ValueError)
114 self.assertIsInstance(res.error_in_exec, ValueError)
115
115
116 def test_open_standard_error_stream(self):
116 def test_open_standard_error_stream(self):
117 res = ip.run_cell("open(2)")
117 res = ip.run_cell("open(2)")
118 self.assertIsInstance(res.error_in_exec, ValueError)
118 self.assertIsInstance(res.error_in_exec, ValueError)
119
119
120 def test_In_variable(self):
120 def test_In_variable(self):
121 "Verify that In variable grows with user input (GH-284)"
121 "Verify that In variable grows with user input (GH-284)"
122 oldlen = len(ip.user_ns['In'])
122 oldlen = len(ip.user_ns['In'])
123 ip.run_cell('1;', store_history=True)
123 ip.run_cell('1;', store_history=True)
124 newlen = len(ip.user_ns['In'])
124 newlen = len(ip.user_ns['In'])
125 self.assertEqual(oldlen+1, newlen)
125 self.assertEqual(oldlen+1, newlen)
126 self.assertEqual(ip.user_ns['In'][-1],'1;')
126 self.assertEqual(ip.user_ns['In'][-1],'1;')
127
127
128 def test_magic_names_in_string(self):
128 def test_magic_names_in_string(self):
129 ip.run_cell('a = """\n%exit\n"""')
129 ip.run_cell('a = """\n%exit\n"""')
130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
131
131
132 def test_trailing_newline(self):
132 def test_trailing_newline(self):
133 """test that running !(command) does not raise a SyntaxError"""
133 """test that running !(command) does not raise a SyntaxError"""
134 ip.run_cell('!(true)\n', False)
134 ip.run_cell('!(true)\n', False)
135 ip.run_cell('!(true)\n\n\n', False)
135 ip.run_cell('!(true)\n\n\n', False)
136
136
137 def test_gh_597(self):
137 def test_gh_597(self):
138 """Pretty-printing lists of objects with non-ascii reprs may cause
138 """Pretty-printing lists of objects with non-ascii reprs may cause
139 problems."""
139 problems."""
140 class Spam(object):
140 class Spam(object):
141 def __repr__(self):
141 def __repr__(self):
142 return "\xe9"*50
142 return "\xe9"*50
143 import IPython.core.formatters
143 import IPython.core.formatters
144 f = IPython.core.formatters.PlainTextFormatter()
144 f = IPython.core.formatters.PlainTextFormatter()
145 f([Spam(),Spam()])
145 f([Spam(),Spam()])
146
146
147
147
148 def test_future_flags(self):
148 def test_future_flags(self):
149 """Check that future flags are used for parsing code (gh-777)"""
149 """Check that future flags are used for parsing code (gh-777)"""
150 ip.run_cell('from __future__ import barry_as_FLUFL')
150 ip.run_cell('from __future__ import barry_as_FLUFL')
151 try:
151 try:
152 ip.run_cell('prfunc_return_val = 1 <> 2')
152 ip.run_cell('prfunc_return_val = 1 <> 2')
153 assert 'prfunc_return_val' in ip.user_ns
153 assert 'prfunc_return_val' in ip.user_ns
154 finally:
154 finally:
155 # Reset compiler flags so we don't mess up other tests.
155 # Reset compiler flags so we don't mess up other tests.
156 ip.compile.reset_compiler_flags()
156 ip.compile.reset_compiler_flags()
157
157
158 def test_can_pickle(self):
158 def test_can_pickle(self):
159 "Can we pickle objects defined interactively (GH-29)"
159 "Can we pickle objects defined interactively (GH-29)"
160 ip = get_ipython()
160 ip = get_ipython()
161 ip.reset()
161 ip.reset()
162 ip.run_cell(("class Mylist(list):\n"
162 ip.run_cell(("class Mylist(list):\n"
163 " def __init__(self,x=[]):\n"
163 " def __init__(self,x=[]):\n"
164 " list.__init__(self,x)"))
164 " list.__init__(self,x)"))
165 ip.run_cell("w=Mylist([1,2,3])")
165 ip.run_cell("w=Mylist([1,2,3])")
166
166
167 from pickle import dumps
167 from pickle import dumps
168
168
169 # We need to swap in our main module - this is only necessary
169 # We need to swap in our main module - this is only necessary
170 # inside the test framework, because IPython puts the interactive module
170 # inside the test framework, because IPython puts the interactive module
171 # in place (but the test framework undoes this).
171 # in place (but the test framework undoes this).
172 _main = sys.modules['__main__']
172 _main = sys.modules['__main__']
173 sys.modules['__main__'] = ip.user_module
173 sys.modules['__main__'] = ip.user_module
174 try:
174 try:
175 res = dumps(ip.user_ns["w"])
175 res = dumps(ip.user_ns["w"])
176 finally:
176 finally:
177 sys.modules['__main__'] = _main
177 sys.modules['__main__'] = _main
178 self.assertTrue(isinstance(res, bytes))
178 self.assertTrue(isinstance(res, bytes))
179
179
180 def test_global_ns(self):
180 def test_global_ns(self):
181 "Code in functions must be able to access variables outside them."
181 "Code in functions must be able to access variables outside them."
182 ip = get_ipython()
182 ip = get_ipython()
183 ip.run_cell("a = 10")
183 ip.run_cell("a = 10")
184 ip.run_cell(("def f(x):\n"
184 ip.run_cell(("def f(x):\n"
185 " return x + a"))
185 " return x + a"))
186 ip.run_cell("b = f(12)")
186 ip.run_cell("b = f(12)")
187 self.assertEqual(ip.user_ns["b"], 22)
187 self.assertEqual(ip.user_ns["b"], 22)
188
188
189 def test_bad_custom_tb(self):
189 def test_bad_custom_tb(self):
190 """Check that InteractiveShell is protected from bad custom exception handlers"""
190 """Check that InteractiveShell is protected from bad custom exception handlers"""
191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 self.assertEqual(ip.custom_exceptions, (IOError,))
192 self.assertEqual(ip.custom_exceptions, (IOError,))
193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
194 ip.run_cell(u'raise IOError("foo")')
194 ip.run_cell(u'raise IOError("foo")')
195 self.assertEqual(ip.custom_exceptions, ())
195 self.assertEqual(ip.custom_exceptions, ())
196
196
197 def test_bad_custom_tb_return(self):
197 def test_bad_custom_tb_return(self):
198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
200 self.assertEqual(ip.custom_exceptions, (NameError,))
200 self.assertEqual(ip.custom_exceptions, (NameError,))
201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
202 ip.run_cell(u'a=abracadabra')
202 ip.run_cell(u'a=abracadabra')
203 self.assertEqual(ip.custom_exceptions, ())
203 self.assertEqual(ip.custom_exceptions, ())
204
204
205 def test_drop_by_id(self):
205 def test_drop_by_id(self):
206 myvars = {"a":object(), "b":object(), "c": object()}
206 myvars = {"a":object(), "b":object(), "c": object()}
207 ip.push(myvars, interactive=False)
207 ip.push(myvars, interactive=False)
208 for name in myvars:
208 for name in myvars:
209 assert name in ip.user_ns, name
209 assert name in ip.user_ns, name
210 assert name in ip.user_ns_hidden, name
210 assert name in ip.user_ns_hidden, name
211 ip.user_ns['b'] = 12
211 ip.user_ns['b'] = 12
212 ip.drop_by_id(myvars)
212 ip.drop_by_id(myvars)
213 for name in ["a", "c"]:
213 for name in ["a", "c"]:
214 assert name not in ip.user_ns, name
214 assert name not in ip.user_ns, name
215 assert name not in ip.user_ns_hidden, name
215 assert name not in ip.user_ns_hidden, name
216 assert ip.user_ns['b'] == 12
216 assert ip.user_ns['b'] == 12
217 ip.reset()
217 ip.reset()
218
218
219 def test_var_expand(self):
219 def test_var_expand(self):
220 ip.user_ns['f'] = u'Ca\xf1o'
220 ip.user_ns['f'] = u'Ca\xf1o'
221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
225
225
226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
227
227
228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
229 # This should not raise any exception:
229 # This should not raise any exception:
230 ip.var_expand(u'echo $f')
230 ip.var_expand(u'echo $f')
231
231
232 def test_var_expand_local(self):
232 def test_var_expand_local(self):
233 """Test local variable expansion in !system and %magic calls"""
233 """Test local variable expansion in !system and %magic calls"""
234 # !system
234 # !system
235 ip.run_cell(
235 ip.run_cell(
236 "def test():\n"
236 "def test():\n"
237 ' lvar = "ttt"\n'
237 ' lvar = "ttt"\n'
238 " ret = !echo {lvar}\n"
238 " ret = !echo {lvar}\n"
239 " return ret[0]\n"
239 " return ret[0]\n"
240 )
240 )
241 res = ip.user_ns["test"]()
241 res = ip.user_ns["test"]()
242 self.assertIn("ttt", res)
242 self.assertIn("ttt", res)
243
243
244 # %magic
244 # %magic
245 ip.run_cell(
245 ip.run_cell(
246 "def makemacro():\n"
246 "def makemacro():\n"
247 ' macroname = "macro_var_expand_locals"\n'
247 ' macroname = "macro_var_expand_locals"\n'
248 " %macro {macroname} codestr\n"
248 " %macro {macroname} codestr\n"
249 )
249 )
250 ip.user_ns["codestr"] = "str(12)"
250 ip.user_ns["codestr"] = "str(12)"
251 ip.run_cell("makemacro()")
251 ip.run_cell("makemacro()")
252 self.assertIn("macro_var_expand_locals", ip.user_ns)
252 self.assertIn("macro_var_expand_locals", ip.user_ns)
253
253
254 def test_var_expand_self(self):
254 def test_var_expand_self(self):
255 """Test variable expansion with the name 'self', which was failing.
255 """Test variable expansion with the name 'self', which was failing.
256
256
257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
258 """
258 """
259 ip.run_cell(
259 ip.run_cell(
260 "class cTest:\n"
260 "class cTest:\n"
261 ' classvar="see me"\n'
261 ' classvar="see me"\n'
262 " def test(self):\n"
262 " def test(self):\n"
263 " res = !echo Variable: {self.classvar}\n"
263 " res = !echo Variable: {self.classvar}\n"
264 " return res[0]\n"
264 " return res[0]\n"
265 )
265 )
266 self.assertIn("see me", ip.user_ns["cTest"]().test())
266 self.assertIn("see me", ip.user_ns["cTest"]().test())
267
267
268 def test_bad_var_expand(self):
268 def test_bad_var_expand(self):
269 """var_expand on invalid formats shouldn't raise"""
269 """var_expand on invalid formats shouldn't raise"""
270 # SyntaxError
270 # SyntaxError
271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
272 # NameError
272 # NameError
273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
274 # ZeroDivisionError
274 # ZeroDivisionError
275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
276
276
277 def test_silent_postexec(self):
277 def test_silent_postexec(self):
278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
279 pre_explicit = mock.Mock()
279 pre_explicit = mock.Mock()
280 pre_always = mock.Mock()
280 pre_always = mock.Mock()
281 post_explicit = mock.Mock()
281 post_explicit = mock.Mock()
282 post_always = mock.Mock()
282 post_always = mock.Mock()
283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
284
284
285 ip.events.register('pre_run_cell', pre_explicit)
285 ip.events.register('pre_run_cell', pre_explicit)
286 ip.events.register('pre_execute', pre_always)
286 ip.events.register('pre_execute', pre_always)
287 ip.events.register('post_run_cell', post_explicit)
287 ip.events.register('post_run_cell', post_explicit)
288 ip.events.register('post_execute', post_always)
288 ip.events.register('post_execute', post_always)
289
289
290 try:
290 try:
291 ip.run_cell("1", silent=True)
291 ip.run_cell("1", silent=True)
292 assert pre_always.called
292 assert pre_always.called
293 assert not pre_explicit.called
293 assert not pre_explicit.called
294 assert post_always.called
294 assert post_always.called
295 assert not post_explicit.called
295 assert not post_explicit.called
296 # double-check that non-silent exec did what we expected
296 # double-check that non-silent exec did what we expected
297 # silent to avoid
297 # silent to avoid
298 ip.run_cell("1")
298 ip.run_cell("1")
299 assert pre_explicit.called
299 assert pre_explicit.called
300 assert post_explicit.called
300 assert post_explicit.called
301 info, = pre_explicit.call_args[0]
301 info, = pre_explicit.call_args[0]
302 result, = post_explicit.call_args[0]
302 result, = post_explicit.call_args[0]
303 self.assertEqual(info, result.info)
303 self.assertEqual(info, result.info)
304 # check that post hooks are always called
304 # check that post hooks are always called
305 [m.reset_mock() for m in all_mocks]
305 [m.reset_mock() for m in all_mocks]
306 ip.run_cell("syntax error")
306 ip.run_cell("syntax error")
307 assert pre_always.called
307 assert pre_always.called
308 assert pre_explicit.called
308 assert pre_explicit.called
309 assert post_always.called
309 assert post_always.called
310 assert post_explicit.called
310 assert post_explicit.called
311 info, = pre_explicit.call_args[0]
311 info, = pre_explicit.call_args[0]
312 result, = post_explicit.call_args[0]
312 result, = post_explicit.call_args[0]
313 self.assertEqual(info, result.info)
313 self.assertEqual(info, result.info)
314 finally:
314 finally:
315 # remove post-exec
315 # remove post-exec
316 ip.events.unregister('pre_run_cell', pre_explicit)
316 ip.events.unregister('pre_run_cell', pre_explicit)
317 ip.events.unregister('pre_execute', pre_always)
317 ip.events.unregister('pre_execute', pre_always)
318 ip.events.unregister('post_run_cell', post_explicit)
318 ip.events.unregister('post_run_cell', post_explicit)
319 ip.events.unregister('post_execute', post_always)
319 ip.events.unregister('post_execute', post_always)
320
320
321 def test_silent_noadvance(self):
321 def test_silent_noadvance(self):
322 """run_cell(silent=True) doesn't advance execution_count"""
322 """run_cell(silent=True) doesn't advance execution_count"""
323 ec = ip.execution_count
323 ec = ip.execution_count
324 # silent should force store_history=False
324 # silent should force store_history=False
325 ip.run_cell("1", store_history=True, silent=True)
325 ip.run_cell("1", store_history=True, silent=True)
326
326
327 self.assertEqual(ec, ip.execution_count)
327 self.assertEqual(ec, ip.execution_count)
328 # double-check that non-silent exec did what we expected
328 # double-check that non-silent exec did what we expected
329 # silent to avoid
329 # silent to avoid
330 ip.run_cell("1", store_history=True)
330 ip.run_cell("1", store_history=True)
331 self.assertEqual(ec+1, ip.execution_count)
331 self.assertEqual(ec+1, ip.execution_count)
332
332
333 def test_silent_nodisplayhook(self):
333 def test_silent_nodisplayhook(self):
334 """run_cell(silent=True) doesn't trigger displayhook"""
334 """run_cell(silent=True) doesn't trigger displayhook"""
335 d = dict(called=False)
335 d = dict(called=False)
336
336
337 trap = ip.display_trap
337 trap = ip.display_trap
338 save_hook = trap.hook
338 save_hook = trap.hook
339
339
340 def failing_hook(*args, **kwargs):
340 def failing_hook(*args, **kwargs):
341 d['called'] = True
341 d['called'] = True
342
342
343 try:
343 try:
344 trap.hook = failing_hook
344 trap.hook = failing_hook
345 res = ip.run_cell("1", silent=True)
345 res = ip.run_cell("1", silent=True)
346 self.assertFalse(d['called'])
346 self.assertFalse(d['called'])
347 self.assertIsNone(res.result)
347 self.assertIsNone(res.result)
348 # double-check that non-silent exec did what we expected
348 # double-check that non-silent exec did what we expected
349 # silent to avoid
349 # silent to avoid
350 ip.run_cell("1")
350 ip.run_cell("1")
351 self.assertTrue(d['called'])
351 self.assertTrue(d['called'])
352 finally:
352 finally:
353 trap.hook = save_hook
353 trap.hook = save_hook
354
354
355 def test_ofind_line_magic(self):
355 def test_ofind_line_magic(self):
356 from IPython.core.magic import register_line_magic
356 from IPython.core.magic import register_line_magic
357
357
358 @register_line_magic
358 @register_line_magic
359 def lmagic(line):
359 def lmagic(line):
360 "A line magic"
360 "A line magic"
361
361
362 # Get info on line magic
362 # Get info on line magic
363 lfind = ip._ofind("lmagic")
363 lfind = ip._ofind("lmagic")
364 info = OInfo(
364 info = OInfo(
365 found=True,
365 found=True,
366 isalias=False,
366 isalias=False,
367 ismagic=True,
367 ismagic=True,
368 namespace="IPython internal",
368 namespace="IPython internal",
369 obj=lmagic,
369 obj=lmagic,
370 parent=None,
370 parent=None,
371 )
371 )
372 self.assertEqual(lfind, info)
372 self.assertEqual(lfind, info)
373
373
374 def test_ofind_cell_magic(self):
374 def test_ofind_cell_magic(self):
375 from IPython.core.magic import register_cell_magic
375 from IPython.core.magic import register_cell_magic
376
376
377 @register_cell_magic
377 @register_cell_magic
378 def cmagic(line, cell):
378 def cmagic(line, cell):
379 "A cell magic"
379 "A cell magic"
380
380
381 # Get info on cell magic
381 # Get info on cell magic
382 find = ip._ofind("cmagic")
382 find = ip._ofind("cmagic")
383 info = OInfo(
383 info = OInfo(
384 found=True,
384 found=True,
385 isalias=False,
385 isalias=False,
386 ismagic=True,
386 ismagic=True,
387 namespace="IPython internal",
387 namespace="IPython internal",
388 obj=cmagic,
388 obj=cmagic,
389 parent=None,
389 parent=None,
390 )
390 )
391 self.assertEqual(find, info)
391 self.assertEqual(find, info)
392
392
393 def test_ofind_property_with_error(self):
393 def test_ofind_property_with_error(self):
394 class A(object):
394 class A(object):
395 @property
395 @property
396 def foo(self):
396 def foo(self):
397 raise NotImplementedError() # pragma: no cover
397 raise NotImplementedError() # pragma: no cover
398
398
399 a = A()
399 a = A()
400
400
401 found = ip._ofind("a.foo", [("locals", locals())])
401 found = ip._ofind("a.foo", [("locals", locals())])
402 info = OInfo(
402 info = OInfo(
403 found=True,
403 found=True,
404 isalias=False,
404 isalias=False,
405 ismagic=False,
405 ismagic=False,
406 namespace="locals",
406 namespace="locals",
407 obj=A.foo,
407 obj=A.foo,
408 parent=a,
408 parent=a,
409 )
409 )
410 self.assertEqual(found, info)
410 self.assertEqual(found, info)
411
411
412 def test_ofind_multiple_attribute_lookups(self):
412 def test_ofind_multiple_attribute_lookups(self):
413 class A(object):
413 class A(object):
414 @property
414 @property
415 def foo(self):
415 def foo(self):
416 raise NotImplementedError() # pragma: no cover
416 raise NotImplementedError() # pragma: no cover
417
417
418 a = A()
418 a = A()
419 a.a = A()
419 a.a = A()
420 a.a.a = A()
420 a.a.a = A()
421
421
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 info = OInfo(
423 info = OInfo(
424 found=True,
424 found=True,
425 isalias=False,
425 isalias=False,
426 ismagic=False,
426 ismagic=False,
427 namespace="locals",
427 namespace="locals",
428 obj=A.foo,
428 obj=A.foo,
429 parent=a.a.a,
429 parent=a.a.a,
430 )
430 )
431 self.assertEqual(found, info)
431 self.assertEqual(found, info)
432
432
433 def test_ofind_slotted_attributes(self):
433 def test_ofind_slotted_attributes(self):
434 class A(object):
434 class A(object):
435 __slots__ = ['foo']
435 __slots__ = ['foo']
436 def __init__(self):
436 def __init__(self):
437 self.foo = 'bar'
437 self.foo = 'bar'
438
438
439 a = A()
439 a = A()
440 found = ip._ofind("a.foo", [("locals", locals())])
440 found = ip._ofind("a.foo", [("locals", locals())])
441 info = OInfo(
441 info = OInfo(
442 found=True,
442 found=True,
443 isalias=False,
443 isalias=False,
444 ismagic=False,
444 ismagic=False,
445 namespace="locals",
445 namespace="locals",
446 obj=a.foo,
446 obj=a.foo,
447 parent=a,
447 parent=a,
448 )
448 )
449 self.assertEqual(found, info)
449 self.assertEqual(found, info)
450
450
451 found = ip._ofind("a.bar", [("locals", locals())])
451 found = ip._ofind("a.bar", [("locals", locals())])
452 info = OInfo(
452 expected = OInfo(
453 found=False,
453 found=False,
454 isalias=False,
454 isalias=False,
455 ismagic=False,
455 ismagic=False,
456 namespace=None,
456 namespace=None,
457 obj=None,
457 obj=None,
458 parent=a,
458 parent=a,
459 )
459 )
460 self.assertEqual(found, info)
460 assert found == expected
461
461
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
463 class A(object):
463 class A(object):
464 @property
464 @property
465 def foo(self):
465 def foo(self):
466 return 'bar'
466 return 'bar'
467 a = A()
467 a = A()
468 a.__dict__["foo"] = "baz"
468 a.__dict__["foo"] = "baz"
469 self.assertEqual(a.foo, "bar")
469 self.assertEqual(a.foo, "bar")
470 found = ip._ofind("a.foo", [("locals", locals())])
470 found = ip._ofind("a.foo", [("locals", locals())])
471 self.assertIs(found.obj, A.foo)
471 self.assertIs(found.obj, A.foo)
472
472
473 def test_custom_syntaxerror_exception(self):
473 def test_custom_syntaxerror_exception(self):
474 called = []
474 called = []
475 def my_handler(shell, etype, value, tb, tb_offset=None):
475 def my_handler(shell, etype, value, tb, tb_offset=None):
476 called.append(etype)
476 called.append(etype)
477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
478
478
479 ip.set_custom_exc((SyntaxError,), my_handler)
479 ip.set_custom_exc((SyntaxError,), my_handler)
480 try:
480 try:
481 ip.run_cell("1f")
481 ip.run_cell("1f")
482 # Check that this was called, and only once.
482 # Check that this was called, and only once.
483 self.assertEqual(called, [SyntaxError])
483 self.assertEqual(called, [SyntaxError])
484 finally:
484 finally:
485 # Reset the custom exception hook
485 # Reset the custom exception hook
486 ip.set_custom_exc((), None)
486 ip.set_custom_exc((), None)
487
487
488 def test_custom_exception(self):
488 def test_custom_exception(self):
489 called = []
489 called = []
490 def my_handler(shell, etype, value, tb, tb_offset=None):
490 def my_handler(shell, etype, value, tb, tb_offset=None):
491 called.append(etype)
491 called.append(etype)
492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
493
493
494 ip.set_custom_exc((ValueError,), my_handler)
494 ip.set_custom_exc((ValueError,), my_handler)
495 try:
495 try:
496 res = ip.run_cell("raise ValueError('test')")
496 res = ip.run_cell("raise ValueError('test')")
497 # Check that this was called, and only once.
497 # Check that this was called, and only once.
498 self.assertEqual(called, [ValueError])
498 self.assertEqual(called, [ValueError])
499 # Check that the error is on the result object
499 # Check that the error is on the result object
500 self.assertIsInstance(res.error_in_exec, ValueError)
500 self.assertIsInstance(res.error_in_exec, ValueError)
501 finally:
501 finally:
502 # Reset the custom exception hook
502 # Reset the custom exception hook
503 ip.set_custom_exc((), None)
503 ip.set_custom_exc((), None)
504
504
505 @mock.patch("builtins.print")
505 @mock.patch("builtins.print")
506 def test_showtraceback_with_surrogates(self, mocked_print):
506 def test_showtraceback_with_surrogates(self, mocked_print):
507 values = []
507 values = []
508
508
509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
510 values.append(value)
510 values.append(value)
511 if value == chr(0xD8FF):
511 if value == chr(0xD8FF):
512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
513
513
514 # mock builtins.print
514 # mock builtins.print
515 mocked_print.side_effect = mock_print_func
515 mocked_print.side_effect = mock_print_func
516
516
517 # ip._showtraceback() is replaced in globalipapp.py.
517 # ip._showtraceback() is replaced in globalipapp.py.
518 # Call original method to test.
518 # Call original method to test.
519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
520
520
521 self.assertEqual(mocked_print.call_count, 2)
521 self.assertEqual(mocked_print.call_count, 2)
522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
523
523
524 def test_mktempfile(self):
524 def test_mktempfile(self):
525 filename = ip.mktempfile()
525 filename = ip.mktempfile()
526 # Check that we can open the file again on Windows
526 # Check that we can open the file again on Windows
527 with open(filename, "w", encoding="utf-8") as f:
527 with open(filename, "w", encoding="utf-8") as f:
528 f.write("abc")
528 f.write("abc")
529
529
530 filename = ip.mktempfile(data="blah")
530 filename = ip.mktempfile(data="blah")
531 with open(filename, "r", encoding="utf-8") as f:
531 with open(filename, "r", encoding="utf-8") as f:
532 self.assertEqual(f.read(), "blah")
532 self.assertEqual(f.read(), "blah")
533
533
534 def test_new_main_mod(self):
534 def test_new_main_mod(self):
535 # Smoketest to check that this accepts a unicode module name
535 # Smoketest to check that this accepts a unicode module name
536 name = u'jiefmw'
536 name = u'jiefmw'
537 mod = ip.new_main_mod(u'%s.py' % name, name)
537 mod = ip.new_main_mod(u'%s.py' % name, name)
538 self.assertEqual(mod.__name__, name)
538 self.assertEqual(mod.__name__, name)
539
539
540 def test_get_exception_only(self):
540 def test_get_exception_only(self):
541 try:
541 try:
542 raise KeyboardInterrupt
542 raise KeyboardInterrupt
543 except KeyboardInterrupt:
543 except KeyboardInterrupt:
544 msg = ip.get_exception_only()
544 msg = ip.get_exception_only()
545 self.assertEqual(msg, 'KeyboardInterrupt\n')
545 self.assertEqual(msg, 'KeyboardInterrupt\n')
546
546
547 try:
547 try:
548 raise DerivedInterrupt("foo")
548 raise DerivedInterrupt("foo")
549 except KeyboardInterrupt:
549 except KeyboardInterrupt:
550 msg = ip.get_exception_only()
550 msg = ip.get_exception_only()
551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
552
552
553 def test_inspect_text(self):
553 def test_inspect_text(self):
554 ip.run_cell('a = 5')
554 ip.run_cell('a = 5')
555 text = ip.object_inspect_text('a')
555 text = ip.object_inspect_text('a')
556 self.assertIsInstance(text, str)
556 self.assertIsInstance(text, str)
557
557
558 def test_last_execution_result(self):
558 def test_last_execution_result(self):
559 """ Check that last execution result gets set correctly (GH-10702) """
559 """ Check that last execution result gets set correctly (GH-10702) """
560 result = ip.run_cell('a = 5; a')
560 result = ip.run_cell('a = 5; a')
561 self.assertTrue(ip.last_execution_succeeded)
561 self.assertTrue(ip.last_execution_succeeded)
562 self.assertEqual(ip.last_execution_result.result, 5)
562 self.assertEqual(ip.last_execution_result.result, 5)
563
563
564 result = ip.run_cell('a = x_invalid_id_x')
564 result = ip.run_cell('a = x_invalid_id_x')
565 self.assertFalse(ip.last_execution_succeeded)
565 self.assertFalse(ip.last_execution_succeeded)
566 self.assertFalse(ip.last_execution_result.success)
566 self.assertFalse(ip.last_execution_result.success)
567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
568
568
569 def test_reset_aliasing(self):
569 def test_reset_aliasing(self):
570 """ Check that standard posix aliases work after %reset. """
570 """ Check that standard posix aliases work after %reset. """
571 if os.name != 'posix':
571 if os.name != 'posix':
572 return
572 return
573
573
574 ip.reset()
574 ip.reset()
575 for cmd in ('clear', 'more', 'less', 'man'):
575 for cmd in ('clear', 'more', 'less', 'man'):
576 res = ip.run_cell('%' + cmd)
576 res = ip.run_cell('%' + cmd)
577 self.assertEqual(res.success, True)
577 self.assertEqual(res.success, True)
578
578
579
579
580 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
580 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
581
581
582 @onlyif_unicode_paths
582 @onlyif_unicode_paths
583 def setUp(self):
583 def setUp(self):
584 self.BASETESTDIR = tempfile.mkdtemp()
584 self.BASETESTDIR = tempfile.mkdtemp()
585 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
585 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
586 os.mkdir(self.TESTDIR)
586 os.mkdir(self.TESTDIR)
587 with open(
587 with open(
588 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
588 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
589 ) as sfile:
589 ) as sfile:
590 sfile.write("pass\n")
590 sfile.write("pass\n")
591 self.oldpath = os.getcwd()
591 self.oldpath = os.getcwd()
592 os.chdir(self.TESTDIR)
592 os.chdir(self.TESTDIR)
593 self.fname = u"åäötestscript.py"
593 self.fname = u"åäötestscript.py"
594
594
595 def tearDown(self):
595 def tearDown(self):
596 os.chdir(self.oldpath)
596 os.chdir(self.oldpath)
597 shutil.rmtree(self.BASETESTDIR)
597 shutil.rmtree(self.BASETESTDIR)
598
598
599 @onlyif_unicode_paths
599 @onlyif_unicode_paths
600 def test_1(self):
600 def test_1(self):
601 """Test safe_execfile with non-ascii path
601 """Test safe_execfile with non-ascii path
602 """
602 """
603 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
603 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
604
604
605 class ExitCodeChecks(tt.TempFileMixin):
605 class ExitCodeChecks(tt.TempFileMixin):
606
606
607 def setUp(self):
607 def setUp(self):
608 self.system = ip.system_raw
608 self.system = ip.system_raw
609
609
610 def test_exit_code_ok(self):
610 def test_exit_code_ok(self):
611 self.system('exit 0')
611 self.system('exit 0')
612 self.assertEqual(ip.user_ns['_exit_code'], 0)
612 self.assertEqual(ip.user_ns['_exit_code'], 0)
613
613
614 def test_exit_code_error(self):
614 def test_exit_code_error(self):
615 self.system('exit 1')
615 self.system('exit 1')
616 self.assertEqual(ip.user_ns['_exit_code'], 1)
616 self.assertEqual(ip.user_ns['_exit_code'], 1)
617
617
618 @skipif(not hasattr(signal, 'SIGALRM'))
618 @skipif(not hasattr(signal, 'SIGALRM'))
619 def test_exit_code_signal(self):
619 def test_exit_code_signal(self):
620 self.mktmp("import signal, time\n"
620 self.mktmp("import signal, time\n"
621 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
621 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
622 "time.sleep(1)\n")
622 "time.sleep(1)\n")
623 self.system("%s %s" % (sys.executable, self.fname))
623 self.system("%s %s" % (sys.executable, self.fname))
624 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
624 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
625
625
626 @onlyif_cmds_exist("csh")
626 @onlyif_cmds_exist("csh")
627 def test_exit_code_signal_csh(self): # pragma: no cover
627 def test_exit_code_signal_csh(self): # pragma: no cover
628 SHELL = os.environ.get("SHELL", None)
628 SHELL = os.environ.get("SHELL", None)
629 os.environ["SHELL"] = find_cmd("csh")
629 os.environ["SHELL"] = find_cmd("csh")
630 try:
630 try:
631 self.test_exit_code_signal()
631 self.test_exit_code_signal()
632 finally:
632 finally:
633 if SHELL is not None:
633 if SHELL is not None:
634 os.environ['SHELL'] = SHELL
634 os.environ['SHELL'] = SHELL
635 else:
635 else:
636 del os.environ['SHELL']
636 del os.environ['SHELL']
637
637
638
638
639 class TestSystemRaw(ExitCodeChecks):
639 class TestSystemRaw(ExitCodeChecks):
640
640
641 def setUp(self):
641 def setUp(self):
642 super().setUp()
642 super().setUp()
643 self.system = ip.system_raw
643 self.system = ip.system_raw
644
644
645 @onlyif_unicode_paths
645 @onlyif_unicode_paths
646 def test_1(self):
646 def test_1(self):
647 """Test system_raw with non-ascii cmd
647 """Test system_raw with non-ascii cmd
648 """
648 """
649 cmd = u'''python -c "'åäö'" '''
649 cmd = u'''python -c "'åäö'" '''
650 ip.system_raw(cmd)
650 ip.system_raw(cmd)
651
651
652 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
652 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
653 @mock.patch('os.system', side_effect=KeyboardInterrupt)
653 @mock.patch('os.system', side_effect=KeyboardInterrupt)
654 def test_control_c(self, *mocks):
654 def test_control_c(self, *mocks):
655 try:
655 try:
656 self.system("sleep 1 # wont happen")
656 self.system("sleep 1 # wont happen")
657 except KeyboardInterrupt: # pragma: no cove
657 except KeyboardInterrupt: # pragma: no cove
658 self.fail(
658 self.fail(
659 "system call should intercept "
659 "system call should intercept "
660 "keyboard interrupt from subprocess.call"
660 "keyboard interrupt from subprocess.call"
661 )
661 )
662 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
662 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
663
663
664
664
665 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
665 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
666 def test_magic_warnings(magic_cmd):
666 def test_magic_warnings(magic_cmd):
667 if sys.platform == "win32":
667 if sys.platform == "win32":
668 to_mock = "os.system"
668 to_mock = "os.system"
669 expected_arg, expected_kwargs = magic_cmd, dict()
669 expected_arg, expected_kwargs = magic_cmd, dict()
670 else:
670 else:
671 to_mock = "subprocess.call"
671 to_mock = "subprocess.call"
672 expected_arg, expected_kwargs = magic_cmd, dict(
672 expected_arg, expected_kwargs = magic_cmd, dict(
673 shell=True, executable=os.environ.get("SHELL", None)
673 shell=True, executable=os.environ.get("SHELL", None)
674 )
674 )
675
675
676 with mock.patch(to_mock, return_value=0) as mock_sub:
676 with mock.patch(to_mock, return_value=0) as mock_sub:
677 with pytest.warns(Warning, match=r"You executed the system command"):
677 with pytest.warns(Warning, match=r"You executed the system command"):
678 ip.system_raw(magic_cmd)
678 ip.system_raw(magic_cmd)
679 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
679 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
680
680
681
681
682 # TODO: Exit codes are currently ignored on Windows.
682 # TODO: Exit codes are currently ignored on Windows.
683 class TestSystemPipedExitCode(ExitCodeChecks):
683 class TestSystemPipedExitCode(ExitCodeChecks):
684
684
685 def setUp(self):
685 def setUp(self):
686 super().setUp()
686 super().setUp()
687 self.system = ip.system_piped
687 self.system = ip.system_piped
688
688
689 @skip_win32
689 @skip_win32
690 def test_exit_code_ok(self):
690 def test_exit_code_ok(self):
691 ExitCodeChecks.test_exit_code_ok(self)
691 ExitCodeChecks.test_exit_code_ok(self)
692
692
693 @skip_win32
693 @skip_win32
694 def test_exit_code_error(self):
694 def test_exit_code_error(self):
695 ExitCodeChecks.test_exit_code_error(self)
695 ExitCodeChecks.test_exit_code_error(self)
696
696
697 @skip_win32
697 @skip_win32
698 def test_exit_code_signal(self):
698 def test_exit_code_signal(self):
699 ExitCodeChecks.test_exit_code_signal(self)
699 ExitCodeChecks.test_exit_code_signal(self)
700
700
701 class TestModules(tt.TempFileMixin):
701 class TestModules(tt.TempFileMixin):
702 def test_extraneous_loads(self):
702 def test_extraneous_loads(self):
703 """Test we're not loading modules on startup that we shouldn't.
703 """Test we're not loading modules on startup that we shouldn't.
704 """
704 """
705 self.mktmp("import sys\n"
705 self.mktmp("import sys\n"
706 "print('numpy' in sys.modules)\n"
706 "print('numpy' in sys.modules)\n"
707 "print('ipyparallel' in sys.modules)\n"
707 "print('ipyparallel' in sys.modules)\n"
708 "print('ipykernel' in sys.modules)\n"
708 "print('ipykernel' in sys.modules)\n"
709 )
709 )
710 out = "False\nFalse\nFalse\n"
710 out = "False\nFalse\nFalse\n"
711 tt.ipexec_validate(self.fname, out)
711 tt.ipexec_validate(self.fname, out)
712
712
713 class Negator(ast.NodeTransformer):
713 class Negator(ast.NodeTransformer):
714 """Negates all number literals in an AST."""
714 """Negates all number literals in an AST."""
715
715
716 # for python 3.7 and earlier
716 # for python 3.7 and earlier
717 def visit_Num(self, node):
717 def visit_Num(self, node):
718 node.n = -node.n
718 node.n = -node.n
719 return node
719 return node
720
720
721 # for python 3.8+
721 # for python 3.8+
722 def visit_Constant(self, node):
722 def visit_Constant(self, node):
723 if isinstance(node.value, int):
723 if isinstance(node.value, int):
724 return self.visit_Num(node)
724 return self.visit_Num(node)
725 return node
725 return node
726
726
727 class TestAstTransform(unittest.TestCase):
727 class TestAstTransform(unittest.TestCase):
728 def setUp(self):
728 def setUp(self):
729 self.negator = Negator()
729 self.negator = Negator()
730 ip.ast_transformers.append(self.negator)
730 ip.ast_transformers.append(self.negator)
731
731
732 def tearDown(self):
732 def tearDown(self):
733 ip.ast_transformers.remove(self.negator)
733 ip.ast_transformers.remove(self.negator)
734
734
735 def test_non_int_const(self):
735 def test_non_int_const(self):
736 with tt.AssertPrints("hello"):
736 with tt.AssertPrints("hello"):
737 ip.run_cell('print("hello")')
737 ip.run_cell('print("hello")')
738
738
739 def test_run_cell(self):
739 def test_run_cell(self):
740 with tt.AssertPrints("-34"):
740 with tt.AssertPrints("-34"):
741 ip.run_cell("print(12 + 22)")
741 ip.run_cell("print(12 + 22)")
742
742
743 # A named reference to a number shouldn't be transformed.
743 # A named reference to a number shouldn't be transformed.
744 ip.user_ns["n"] = 55
744 ip.user_ns["n"] = 55
745 with tt.AssertNotPrints("-55"):
745 with tt.AssertNotPrints("-55"):
746 ip.run_cell("print(n)")
746 ip.run_cell("print(n)")
747
747
748 def test_timeit(self):
748 def test_timeit(self):
749 called = set()
749 called = set()
750 def f(x):
750 def f(x):
751 called.add(x)
751 called.add(x)
752 ip.push({'f':f})
752 ip.push({'f':f})
753
753
754 with tt.AssertPrints("std. dev. of"):
754 with tt.AssertPrints("std. dev. of"):
755 ip.run_line_magic("timeit", "-n1 f(1)")
755 ip.run_line_magic("timeit", "-n1 f(1)")
756 self.assertEqual(called, {-1})
756 self.assertEqual(called, {-1})
757 called.clear()
757 called.clear()
758
758
759 with tt.AssertPrints("std. dev. of"):
759 with tt.AssertPrints("std. dev. of"):
760 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
760 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
761 self.assertEqual(called, {-2, -3})
761 self.assertEqual(called, {-2, -3})
762
762
763 def test_time(self):
763 def test_time(self):
764 called = []
764 called = []
765 def f(x):
765 def f(x):
766 called.append(x)
766 called.append(x)
767 ip.push({'f':f})
767 ip.push({'f':f})
768
768
769 # Test with an expression
769 # Test with an expression
770 with tt.AssertPrints("Wall time: "):
770 with tt.AssertPrints("Wall time: "):
771 ip.run_line_magic("time", "f(5+9)")
771 ip.run_line_magic("time", "f(5+9)")
772 self.assertEqual(called, [-14])
772 self.assertEqual(called, [-14])
773 called[:] = []
773 called[:] = []
774
774
775 # Test with a statement (different code path)
775 # Test with a statement (different code path)
776 with tt.AssertPrints("Wall time: "):
776 with tt.AssertPrints("Wall time: "):
777 ip.run_line_magic("time", "a = f(-3 + -2)")
777 ip.run_line_magic("time", "a = f(-3 + -2)")
778 self.assertEqual(called, [5])
778 self.assertEqual(called, [5])
779
779
780 def test_macro(self):
780 def test_macro(self):
781 ip.push({'a':10})
781 ip.push({'a':10})
782 # The AST transformation makes this do a+=-1
782 # The AST transformation makes this do a+=-1
783 ip.define_macro("amacro", "a+=1\nprint(a)")
783 ip.define_macro("amacro", "a+=1\nprint(a)")
784
784
785 with tt.AssertPrints("9"):
785 with tt.AssertPrints("9"):
786 ip.run_cell("amacro")
786 ip.run_cell("amacro")
787 with tt.AssertPrints("8"):
787 with tt.AssertPrints("8"):
788 ip.run_cell("amacro")
788 ip.run_cell("amacro")
789
789
790 class TestMiscTransform(unittest.TestCase):
790 class TestMiscTransform(unittest.TestCase):
791
791
792
792
793 def test_transform_only_once(self):
793 def test_transform_only_once(self):
794 cleanup = 0
794 cleanup = 0
795 line_t = 0
795 line_t = 0
796 def count_cleanup(lines):
796 def count_cleanup(lines):
797 nonlocal cleanup
797 nonlocal cleanup
798 cleanup += 1
798 cleanup += 1
799 return lines
799 return lines
800
800
801 def count_line_t(lines):
801 def count_line_t(lines):
802 nonlocal line_t
802 nonlocal line_t
803 line_t += 1
803 line_t += 1
804 return lines
804 return lines
805
805
806 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
806 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
807 ip.input_transformer_manager.line_transforms.append(count_line_t)
807 ip.input_transformer_manager.line_transforms.append(count_line_t)
808
808
809 ip.run_cell('1')
809 ip.run_cell('1')
810
810
811 assert cleanup == 1
811 assert cleanup == 1
812 assert line_t == 1
812 assert line_t == 1
813
813
814 class IntegerWrapper(ast.NodeTransformer):
814 class IntegerWrapper(ast.NodeTransformer):
815 """Wraps all integers in a call to Integer()"""
815 """Wraps all integers in a call to Integer()"""
816
816
817 # for Python 3.7 and earlier
817 # for Python 3.7 and earlier
818
818
819 # for Python 3.7 and earlier
819 # for Python 3.7 and earlier
820 def visit_Num(self, node):
820 def visit_Num(self, node):
821 if isinstance(node.n, int):
821 if isinstance(node.n, int):
822 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
822 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
823 args=[node], keywords=[])
823 args=[node], keywords=[])
824 return node
824 return node
825
825
826 # For Python 3.8+
826 # For Python 3.8+
827 def visit_Constant(self, node):
827 def visit_Constant(self, node):
828 if isinstance(node.value, int):
828 if isinstance(node.value, int):
829 return self.visit_Num(node)
829 return self.visit_Num(node)
830 return node
830 return node
831
831
832
832
833 class TestAstTransform2(unittest.TestCase):
833 class TestAstTransform2(unittest.TestCase):
834 def setUp(self):
834 def setUp(self):
835 self.intwrapper = IntegerWrapper()
835 self.intwrapper = IntegerWrapper()
836 ip.ast_transformers.append(self.intwrapper)
836 ip.ast_transformers.append(self.intwrapper)
837
837
838 self.calls = []
838 self.calls = []
839 def Integer(*args):
839 def Integer(*args):
840 self.calls.append(args)
840 self.calls.append(args)
841 return args
841 return args
842 ip.push({"Integer": Integer})
842 ip.push({"Integer": Integer})
843
843
844 def tearDown(self):
844 def tearDown(self):
845 ip.ast_transformers.remove(self.intwrapper)
845 ip.ast_transformers.remove(self.intwrapper)
846 del ip.user_ns['Integer']
846 del ip.user_ns['Integer']
847
847
848 def test_run_cell(self):
848 def test_run_cell(self):
849 ip.run_cell("n = 2")
849 ip.run_cell("n = 2")
850 self.assertEqual(self.calls, [(2,)])
850 self.assertEqual(self.calls, [(2,)])
851
851
852 # This shouldn't throw an error
852 # This shouldn't throw an error
853 ip.run_cell("o = 2.0")
853 ip.run_cell("o = 2.0")
854 self.assertEqual(ip.user_ns['o'], 2.0)
854 self.assertEqual(ip.user_ns['o'], 2.0)
855
855
856 def test_run_cell_non_int(self):
856 def test_run_cell_non_int(self):
857 ip.run_cell("n = 'a'")
857 ip.run_cell("n = 'a'")
858 assert self.calls == []
858 assert self.calls == []
859
859
860 def test_timeit(self):
860 def test_timeit(self):
861 called = set()
861 called = set()
862 def f(x):
862 def f(x):
863 called.add(x)
863 called.add(x)
864 ip.push({'f':f})
864 ip.push({'f':f})
865
865
866 with tt.AssertPrints("std. dev. of"):
866 with tt.AssertPrints("std. dev. of"):
867 ip.run_line_magic("timeit", "-n1 f(1)")
867 ip.run_line_magic("timeit", "-n1 f(1)")
868 self.assertEqual(called, {(1,)})
868 self.assertEqual(called, {(1,)})
869 called.clear()
869 called.clear()
870
870
871 with tt.AssertPrints("std. dev. of"):
871 with tt.AssertPrints("std. dev. of"):
872 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
872 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
873 self.assertEqual(called, {(2,), (3,)})
873 self.assertEqual(called, {(2,), (3,)})
874
874
875 class ErrorTransformer(ast.NodeTransformer):
875 class ErrorTransformer(ast.NodeTransformer):
876 """Throws an error when it sees a number."""
876 """Throws an error when it sees a number."""
877
877
878 def visit_Constant(self, node):
878 def visit_Constant(self, node):
879 if isinstance(node.value, int):
879 if isinstance(node.value, int):
880 raise ValueError("test")
880 raise ValueError("test")
881 return node
881 return node
882
882
883
883
884 class TestAstTransformError(unittest.TestCase):
884 class TestAstTransformError(unittest.TestCase):
885 def test_unregistering(self):
885 def test_unregistering(self):
886 err_transformer = ErrorTransformer()
886 err_transformer = ErrorTransformer()
887 ip.ast_transformers.append(err_transformer)
887 ip.ast_transformers.append(err_transformer)
888
888
889 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
889 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
890 ip.run_cell("1 + 2")
890 ip.run_cell("1 + 2")
891
891
892 # This should have been removed.
892 # This should have been removed.
893 self.assertNotIn(err_transformer, ip.ast_transformers)
893 self.assertNotIn(err_transformer, ip.ast_transformers)
894
894
895
895
896 class StringRejector(ast.NodeTransformer):
896 class StringRejector(ast.NodeTransformer):
897 """Throws an InputRejected when it sees a string literal.
897 """Throws an InputRejected when it sees a string literal.
898
898
899 Used to verify that NodeTransformers can signal that a piece of code should
899 Used to verify that NodeTransformers can signal that a piece of code should
900 not be executed by throwing an InputRejected.
900 not be executed by throwing an InputRejected.
901 """
901 """
902
902
903 # 3.8 only
903 # 3.8 only
904 def visit_Constant(self, node):
904 def visit_Constant(self, node):
905 if isinstance(node.value, str):
905 if isinstance(node.value, str):
906 raise InputRejected("test")
906 raise InputRejected("test")
907 return node
907 return node
908
908
909
909
910 class TestAstTransformInputRejection(unittest.TestCase):
910 class TestAstTransformInputRejection(unittest.TestCase):
911
911
912 def setUp(self):
912 def setUp(self):
913 self.transformer = StringRejector()
913 self.transformer = StringRejector()
914 ip.ast_transformers.append(self.transformer)
914 ip.ast_transformers.append(self.transformer)
915
915
916 def tearDown(self):
916 def tearDown(self):
917 ip.ast_transformers.remove(self.transformer)
917 ip.ast_transformers.remove(self.transformer)
918
918
919 def test_input_rejection(self):
919 def test_input_rejection(self):
920 """Check that NodeTransformers can reject input."""
920 """Check that NodeTransformers can reject input."""
921
921
922 expect_exception_tb = tt.AssertPrints("InputRejected: test")
922 expect_exception_tb = tt.AssertPrints("InputRejected: test")
923 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
923 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
924
924
925 # Run the same check twice to verify that the transformer is not
925 # Run the same check twice to verify that the transformer is not
926 # disabled after raising.
926 # disabled after raising.
927 with expect_exception_tb, expect_no_cell_output:
927 with expect_exception_tb, expect_no_cell_output:
928 ip.run_cell("'unsafe'")
928 ip.run_cell("'unsafe'")
929
929
930 with expect_exception_tb, expect_no_cell_output:
930 with expect_exception_tb, expect_no_cell_output:
931 res = ip.run_cell("'unsafe'")
931 res = ip.run_cell("'unsafe'")
932
932
933 self.assertIsInstance(res.error_before_exec, InputRejected)
933 self.assertIsInstance(res.error_before_exec, InputRejected)
934
934
935 def test__IPYTHON__():
935 def test__IPYTHON__():
936 # This shouldn't raise a NameError, that's all
936 # This shouldn't raise a NameError, that's all
937 __IPYTHON__
937 __IPYTHON__
938
938
939
939
940 class DummyRepr(object):
940 class DummyRepr(object):
941 def __repr__(self):
941 def __repr__(self):
942 return "DummyRepr"
942 return "DummyRepr"
943
943
944 def _repr_html_(self):
944 def _repr_html_(self):
945 return "<b>dummy</b>"
945 return "<b>dummy</b>"
946
946
947 def _repr_javascript_(self):
947 def _repr_javascript_(self):
948 return "console.log('hi');", {'key': 'value'}
948 return "console.log('hi');", {'key': 'value'}
949
949
950
950
951 def test_user_variables():
951 def test_user_variables():
952 # enable all formatters
952 # enable all formatters
953 ip.display_formatter.active_types = ip.display_formatter.format_types
953 ip.display_formatter.active_types = ip.display_formatter.format_types
954
954
955 ip.user_ns['dummy'] = d = DummyRepr()
955 ip.user_ns['dummy'] = d = DummyRepr()
956 keys = {'dummy', 'doesnotexist'}
956 keys = {'dummy', 'doesnotexist'}
957 r = ip.user_expressions({ key:key for key in keys})
957 r = ip.user_expressions({ key:key for key in keys})
958
958
959 assert keys == set(r.keys())
959 assert keys == set(r.keys())
960 dummy = r["dummy"]
960 dummy = r["dummy"]
961 assert {"status", "data", "metadata"} == set(dummy.keys())
961 assert {"status", "data", "metadata"} == set(dummy.keys())
962 assert dummy["status"] == "ok"
962 assert dummy["status"] == "ok"
963 data = dummy["data"]
963 data = dummy["data"]
964 metadata = dummy["metadata"]
964 metadata = dummy["metadata"]
965 assert data.get("text/html") == d._repr_html_()
965 assert data.get("text/html") == d._repr_html_()
966 js, jsmd = d._repr_javascript_()
966 js, jsmd = d._repr_javascript_()
967 assert data.get("application/javascript") == js
967 assert data.get("application/javascript") == js
968 assert metadata.get("application/javascript") == jsmd
968 assert metadata.get("application/javascript") == jsmd
969
969
970 dne = r["doesnotexist"]
970 dne = r["doesnotexist"]
971 assert dne["status"] == "error"
971 assert dne["status"] == "error"
972 assert dne["ename"] == "NameError"
972 assert dne["ename"] == "NameError"
973
973
974 # back to text only
974 # back to text only
975 ip.display_formatter.active_types = ['text/plain']
975 ip.display_formatter.active_types = ['text/plain']
976
976
977 def test_user_expression():
977 def test_user_expression():
978 # enable all formatters
978 # enable all formatters
979 ip.display_formatter.active_types = ip.display_formatter.format_types
979 ip.display_formatter.active_types = ip.display_formatter.format_types
980 query = {
980 query = {
981 'a' : '1 + 2',
981 'a' : '1 + 2',
982 'b' : '1/0',
982 'b' : '1/0',
983 }
983 }
984 r = ip.user_expressions(query)
984 r = ip.user_expressions(query)
985 import pprint
985 import pprint
986 pprint.pprint(r)
986 pprint.pprint(r)
987 assert set(r.keys()) == set(query.keys())
987 assert set(r.keys()) == set(query.keys())
988 a = r["a"]
988 a = r["a"]
989 assert {"status", "data", "metadata"} == set(a.keys())
989 assert {"status", "data", "metadata"} == set(a.keys())
990 assert a["status"] == "ok"
990 assert a["status"] == "ok"
991 data = a["data"]
991 data = a["data"]
992 metadata = a["metadata"]
992 metadata = a["metadata"]
993 assert data.get("text/plain") == "3"
993 assert data.get("text/plain") == "3"
994
994
995 b = r["b"]
995 b = r["b"]
996 assert b["status"] == "error"
996 assert b["status"] == "error"
997 assert b["ename"] == "ZeroDivisionError"
997 assert b["ename"] == "ZeroDivisionError"
998
998
999 # back to text only
999 # back to text only
1000 ip.display_formatter.active_types = ['text/plain']
1000 ip.display_formatter.active_types = ['text/plain']
1001
1001
1002
1002
1003 class TestSyntaxErrorTransformer(unittest.TestCase):
1003 class TestSyntaxErrorTransformer(unittest.TestCase):
1004 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1004 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1005
1005
1006 @staticmethod
1006 @staticmethod
1007 def transformer(lines):
1007 def transformer(lines):
1008 for line in lines:
1008 for line in lines:
1009 pos = line.find('syntaxerror')
1009 pos = line.find('syntaxerror')
1010 if pos >= 0:
1010 if pos >= 0:
1011 e = SyntaxError('input contains "syntaxerror"')
1011 e = SyntaxError('input contains "syntaxerror"')
1012 e.text = line
1012 e.text = line
1013 e.offset = pos + 1
1013 e.offset = pos + 1
1014 raise e
1014 raise e
1015 return lines
1015 return lines
1016
1016
1017 def setUp(self):
1017 def setUp(self):
1018 ip.input_transformers_post.append(self.transformer)
1018 ip.input_transformers_post.append(self.transformer)
1019
1019
1020 def tearDown(self):
1020 def tearDown(self):
1021 ip.input_transformers_post.remove(self.transformer)
1021 ip.input_transformers_post.remove(self.transformer)
1022
1022
1023 def test_syntaxerror_input_transformer(self):
1023 def test_syntaxerror_input_transformer(self):
1024 with tt.AssertPrints('1234'):
1024 with tt.AssertPrints('1234'):
1025 ip.run_cell('1234')
1025 ip.run_cell('1234')
1026 with tt.AssertPrints('SyntaxError: invalid syntax'):
1026 with tt.AssertPrints('SyntaxError: invalid syntax'):
1027 ip.run_cell('1 2 3') # plain python syntax error
1027 ip.run_cell('1 2 3') # plain python syntax error
1028 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1028 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1029 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1029 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1030 with tt.AssertPrints('3456'):
1030 with tt.AssertPrints('3456'):
1031 ip.run_cell('3456')
1031 ip.run_cell('3456')
1032
1032
1033
1033
1034 class TestWarningSuppression(unittest.TestCase):
1034 class TestWarningSuppression(unittest.TestCase):
1035 def test_warning_suppression(self):
1035 def test_warning_suppression(self):
1036 ip.run_cell("import warnings")
1036 ip.run_cell("import warnings")
1037 try:
1037 try:
1038 with self.assertWarnsRegex(UserWarning, "asdf"):
1038 with self.assertWarnsRegex(UserWarning, "asdf"):
1039 ip.run_cell("warnings.warn('asdf')")
1039 ip.run_cell("warnings.warn('asdf')")
1040 # Here's the real test -- if we run that again, we should get the
1040 # Here's the real test -- if we run that again, we should get the
1041 # warning again. Traditionally, each warning was only issued once per
1041 # warning again. Traditionally, each warning was only issued once per
1042 # IPython session (approximately), even if the user typed in new and
1042 # IPython session (approximately), even if the user typed in new and
1043 # different code that should have also triggered the warning, leading
1043 # different code that should have also triggered the warning, leading
1044 # to much confusion.
1044 # to much confusion.
1045 with self.assertWarnsRegex(UserWarning, "asdf"):
1045 with self.assertWarnsRegex(UserWarning, "asdf"):
1046 ip.run_cell("warnings.warn('asdf')")
1046 ip.run_cell("warnings.warn('asdf')")
1047 finally:
1047 finally:
1048 ip.run_cell("del warnings")
1048 ip.run_cell("del warnings")
1049
1049
1050
1050
1051 def test_deprecation_warning(self):
1051 def test_deprecation_warning(self):
1052 ip.run_cell("""
1052 ip.run_cell("""
1053 import warnings
1053 import warnings
1054 def wrn():
1054 def wrn():
1055 warnings.warn(
1055 warnings.warn(
1056 "I AM A WARNING",
1056 "I AM A WARNING",
1057 DeprecationWarning
1057 DeprecationWarning
1058 )
1058 )
1059 """)
1059 """)
1060 try:
1060 try:
1061 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1061 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1062 ip.run_cell("wrn()")
1062 ip.run_cell("wrn()")
1063 finally:
1063 finally:
1064 ip.run_cell("del warnings")
1064 ip.run_cell("del warnings")
1065 ip.run_cell("del wrn")
1065 ip.run_cell("del wrn")
1066
1066
1067
1067
1068 class TestImportNoDeprecate(tt.TempFileMixin):
1068 class TestImportNoDeprecate(tt.TempFileMixin):
1069
1069
1070 def setUp(self):
1070 def setUp(self):
1071 """Make a valid python temp file."""
1071 """Make a valid python temp file."""
1072 self.mktmp("""
1072 self.mktmp("""
1073 import warnings
1073 import warnings
1074 def wrn():
1074 def wrn():
1075 warnings.warn(
1075 warnings.warn(
1076 "I AM A WARNING",
1076 "I AM A WARNING",
1077 DeprecationWarning
1077 DeprecationWarning
1078 )
1078 )
1079 """)
1079 """)
1080 super().setUp()
1080 super().setUp()
1081
1081
1082 def test_no_dep(self):
1082 def test_no_dep(self):
1083 """
1083 """
1084 No deprecation warning should be raised from imported functions
1084 No deprecation warning should be raised from imported functions
1085 """
1085 """
1086 ip.run_cell("from {} import wrn".format(self.fname))
1086 ip.run_cell("from {} import wrn".format(self.fname))
1087
1087
1088 with tt.AssertNotPrints("I AM A WARNING"):
1088 with tt.AssertNotPrints("I AM A WARNING"):
1089 ip.run_cell("wrn()")
1089 ip.run_cell("wrn()")
1090 ip.run_cell("del wrn")
1090 ip.run_cell("del wrn")
1091
1091
1092
1092
1093 def test_custom_exc_count():
1093 def test_custom_exc_count():
1094 hook = mock.Mock(return_value=None)
1094 hook = mock.Mock(return_value=None)
1095 ip.set_custom_exc((SyntaxError,), hook)
1095 ip.set_custom_exc((SyntaxError,), hook)
1096 before = ip.execution_count
1096 before = ip.execution_count
1097 ip.run_cell("def foo()", store_history=True)
1097 ip.run_cell("def foo()", store_history=True)
1098 # restore default excepthook
1098 # restore default excepthook
1099 ip.set_custom_exc((), None)
1099 ip.set_custom_exc((), None)
1100 assert hook.call_count == 1
1100 assert hook.call_count == 1
1101 assert ip.execution_count == before + 1
1101 assert ip.execution_count == before + 1
1102
1102
1103
1103
1104 def test_run_cell_async():
1104 def test_run_cell_async():
1105 ip.run_cell("import asyncio")
1105 ip.run_cell("import asyncio")
1106 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1106 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1107 assert asyncio.iscoroutine(coro)
1107 assert asyncio.iscoroutine(coro)
1108 loop = asyncio.new_event_loop()
1108 loop = asyncio.new_event_loop()
1109 result = loop.run_until_complete(coro)
1109 result = loop.run_until_complete(coro)
1110 assert isinstance(result, interactiveshell.ExecutionResult)
1110 assert isinstance(result, interactiveshell.ExecutionResult)
1111 assert result.result == 5
1111 assert result.result == 5
1112
1112
1113
1113
1114 def test_run_cell_await():
1114 def test_run_cell_await():
1115 ip.run_cell("import asyncio")
1115 ip.run_cell("import asyncio")
1116 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1116 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1117 assert ip.user_ns["_"] == 10
1117 assert ip.user_ns["_"] == 10
1118
1118
1119
1119
1120 def test_run_cell_asyncio_run():
1120 def test_run_cell_asyncio_run():
1121 ip.run_cell("import asyncio")
1121 ip.run_cell("import asyncio")
1122 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1122 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1123 assert ip.user_ns["_"] == 1
1123 assert ip.user_ns["_"] == 1
1124 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1124 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1125 assert ip.user_ns["_"] == 2
1125 assert ip.user_ns["_"] == 2
1126 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1126 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1127 assert ip.user_ns["_"] == 3
1127 assert ip.user_ns["_"] == 3
1128
1128
1129
1129
1130 def test_should_run_async():
1130 def test_should_run_async():
1131 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1131 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1132 assert ip.should_run_async("await x", transformed_cell="await x")
1132 assert ip.should_run_async("await x", transformed_cell="await x")
1133 assert ip.should_run_async(
1133 assert ip.should_run_async(
1134 "import asyncio; await asyncio.sleep(1)",
1134 "import asyncio; await asyncio.sleep(1)",
1135 transformed_cell="import asyncio; await asyncio.sleep(1)",
1135 transformed_cell="import asyncio; await asyncio.sleep(1)",
1136 )
1136 )
1137
1137
1138
1138
1139 def test_set_custom_completer():
1139 def test_set_custom_completer():
1140 num_completers = len(ip.Completer.matchers)
1140 num_completers = len(ip.Completer.matchers)
1141
1141
1142 def foo(*args, **kwargs):
1142 def foo(*args, **kwargs):
1143 return "I'm a completer!"
1143 return "I'm a completer!"
1144
1144
1145 ip.set_custom_completer(foo, 0)
1145 ip.set_custom_completer(foo, 0)
1146
1146
1147 # check that we've really added a new completer
1147 # check that we've really added a new completer
1148 assert len(ip.Completer.matchers) == num_completers + 1
1148 assert len(ip.Completer.matchers) == num_completers + 1
1149
1149
1150 # check that the first completer is the function we defined
1150 # check that the first completer is the function we defined
1151 assert ip.Completer.matchers[0]() == "I'm a completer!"
1151 assert ip.Completer.matchers[0]() == "I'm a completer!"
1152
1152
1153 # clean up
1153 # clean up
1154 ip.Completer.custom_matchers.pop()
1154 ip.Completer.custom_matchers.pop()
1155
1155
1156
1156
1157 class TestShowTracebackAttack(unittest.TestCase):
1157 class TestShowTracebackAttack(unittest.TestCase):
1158 """Test that the interactive shell is resilient against the client attack of
1158 """Test that the interactive shell is resilient against the client attack of
1159 manipulating the showtracebacks method. These attacks shouldn't result in an
1159 manipulating the showtracebacks method. These attacks shouldn't result in an
1160 unhandled exception in the kernel."""
1160 unhandled exception in the kernel."""
1161
1161
1162 def setUp(self):
1162 def setUp(self):
1163 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1163 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1164
1164
1165 def tearDown(self):
1165 def tearDown(self):
1166 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1166 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1167
1167
1168 def test_set_show_tracebacks_none(self):
1168 def test_set_show_tracebacks_none(self):
1169 """Test the case of the client setting showtracebacks to None"""
1169 """Test the case of the client setting showtracebacks to None"""
1170
1170
1171 result = ip.run_cell(
1171 result = ip.run_cell(
1172 """
1172 """
1173 import IPython.core.interactiveshell
1173 import IPython.core.interactiveshell
1174 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1174 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1175
1175
1176 assert False, "This should not raise an exception"
1176 assert False, "This should not raise an exception"
1177 """
1177 """
1178 )
1178 )
1179 print(result)
1179 print(result)
1180
1180
1181 assert result.result is None
1181 assert result.result is None
1182 assert isinstance(result.error_in_exec, TypeError)
1182 assert isinstance(result.error_in_exec, TypeError)
1183 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1183 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1184
1184
1185 def test_set_show_tracebacks_noop(self):
1185 def test_set_show_tracebacks_noop(self):
1186 """Test the case of the client setting showtracebacks to a no op lambda"""
1186 """Test the case of the client setting showtracebacks to a no op lambda"""
1187
1187
1188 result = ip.run_cell(
1188 result = ip.run_cell(
1189 """
1189 """
1190 import IPython.core.interactiveshell
1190 import IPython.core.interactiveshell
1191 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1191 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1192
1192
1193 assert False, "This should not raise an exception"
1193 assert False, "This should not raise an exception"
1194 """
1194 """
1195 )
1195 )
1196 print(result)
1196 print(result)
1197
1197
1198 assert result.result is None
1198 assert result.result is None
1199 assert isinstance(result.error_in_exec, AssertionError)
1199 assert isinstance(result.error_in_exec, AssertionError)
1200 assert str(result.error_in_exec) == "This should not raise an exception"
1200 assert str(result.error_in_exec) == "This should not raise an exception"
@@ -1,1377 +1,1395 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Verbose and colourful traceback formatting.
3 Verbose and colourful traceback formatting.
4
4
5 **ColorTB**
5 **ColorTB**
6
6
7 I've always found it a bit hard to visually parse tracebacks in Python. The
7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 ColorTB class is a solution to that problem. It colors the different parts of a
8 ColorTB class is a solution to that problem. It colors the different parts of a
9 traceback in a manner similar to what you would expect from a syntax-highlighting
9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 text editor.
10 text editor.
11
11
12 Installation instructions for ColorTB::
12 Installation instructions for ColorTB::
13
13
14 import sys,ultratb
14 import sys,ultratb
15 sys.excepthook = ultratb.ColorTB()
15 sys.excepthook = ultratb.ColorTB()
16
16
17 **VerboseTB**
17 **VerboseTB**
18
18
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 and intended it for CGI programmers, but why should they have all the fun? I
21 and intended it for CGI programmers, but why should they have all the fun? I
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 but kind of neat, and maybe useful for long-running programs that you believe
23 but kind of neat, and maybe useful for long-running programs that you believe
24 are bug-free. If a crash *does* occur in that type of program you want details.
24 are bug-free. If a crash *does* occur in that type of program you want details.
25 Give it a shot--you'll love it or you'll hate it.
25 Give it a shot--you'll love it or you'll hate it.
26
26
27 .. note::
27 .. note::
28
28
29 The Verbose mode prints the variables currently visible where the exception
29 The Verbose mode prints the variables currently visible where the exception
30 happened (shortening their strings if too long). This can potentially be
30 happened (shortening their strings if too long). This can potentially be
31 very slow, if you happen to have a huge data structure whose string
31 very slow, if you happen to have a huge data structure whose string
32 representation is complex to compute. Your computer may appear to freeze for
32 representation is complex to compute. Your computer may appear to freeze for
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 with Ctrl-C (maybe hitting it more than once).
34 with Ctrl-C (maybe hitting it more than once).
35
35
36 If you encounter this kind of situation often, you may want to use the
36 If you encounter this kind of situation often, you may want to use the
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 variables (but otherwise includes the information and context given by
38 variables (but otherwise includes the information and context given by
39 Verbose).
39 Verbose).
40
40
41 .. note::
41 .. note::
42
42
43 The verbose mode print all variables in the stack, which means it can
43 The verbose mode print all variables in the stack, which means it can
44 potentially leak sensitive information like access keys, or unencrypted
44 potentially leak sensitive information like access keys, or unencrypted
45 password.
45 password.
46
46
47 Installation instructions for VerboseTB::
47 Installation instructions for VerboseTB::
48
48
49 import sys,ultratb
49 import sys,ultratb
50 sys.excepthook = ultratb.VerboseTB()
50 sys.excepthook = ultratb.VerboseTB()
51
51
52 Note: Much of the code in this module was lifted verbatim from the standard
52 Note: Much of the code in this module was lifted verbatim from the standard
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54
54
55 Color schemes
55 Color schemes
56 -------------
56 -------------
57
57
58 The colors are defined in the class TBTools through the use of the
58 The colors are defined in the class TBTools through the use of the
59 ColorSchemeTable class. Currently the following exist:
59 ColorSchemeTable class. Currently the following exist:
60
60
61 - NoColor: allows all of this module to be used in any terminal (the color
61 - NoColor: allows all of this module to be used in any terminal (the color
62 escapes are just dummy blank strings).
62 escapes are just dummy blank strings).
63
63
64 - Linux: is meant to look good in a terminal like the Linux console (black
64 - Linux: is meant to look good in a terminal like the Linux console (black
65 or very dark background).
65 or very dark background).
66
66
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 in light background terminals.
68 in light background terminals.
69
69
70 - Neutral: a neutral color scheme that should be readable on both light and
70 - Neutral: a neutral color scheme that should be readable on both light and
71 dark background
71 dark background
72
72
73 You can implement other color schemes easily, the syntax is fairly
73 You can implement other color schemes easily, the syntax is fairly
74 self-explanatory. Please send back new schemes you develop to the author for
74 self-explanatory. Please send back new schemes you develop to the author for
75 possible inclusion in future releases.
75 possible inclusion in future releases.
76
76
77 Inheritance diagram:
77 Inheritance diagram:
78
78
79 .. inheritance-diagram:: IPython.core.ultratb
79 .. inheritance-diagram:: IPython.core.ultratb
80 :parts: 3
80 :parts: 3
81 """
81 """
82
82
83 #*****************************************************************************
83 #*****************************************************************************
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 #
86 #
87 # Distributed under the terms of the BSD License. The full license is in
87 # Distributed under the terms of the BSD License. The full license is in
88 # the file COPYING, distributed as part of this software.
88 # the file COPYING, distributed as part of this software.
89 #*****************************************************************************
89 #*****************************************************************************
90
90
91
91
92 import inspect
92 import inspect
93 import linecache
93 import linecache
94 import pydoc
94 import pydoc
95 import sys
95 import sys
96 import time
96 import time
97 import traceback
97 import traceback
98 from types import TracebackType
98 from types import TracebackType
99 from typing import Tuple, List, Any, Optional
99 from typing import Tuple, List, Any, Optional
100
100
101 import stack_data
101 import stack_data
102 from stack_data import FrameInfo as SDFrameInfo
102 from stack_data import FrameInfo as SDFrameInfo
103 from pygments.formatters.terminal256 import Terminal256Formatter
103 from pygments.formatters.terminal256 import Terminal256Formatter
104 from pygments.styles import get_style_by_name
104 from pygments.styles import get_style_by_name
105
105
106 # IPython's own modules
106 # IPython's own modules
107 from IPython import get_ipython
107 from IPython import get_ipython
108 from IPython.core import debugger
108 from IPython.core import debugger
109 from IPython.core.display_trap import DisplayTrap
109 from IPython.core.display_trap import DisplayTrap
110 from IPython.core.excolors import exception_colors
110 from IPython.core.excolors import exception_colors
111 from IPython.utils import PyColorize
111 from IPython.utils import PyColorize
112 from IPython.utils import path as util_path
112 from IPython.utils import path as util_path
113 from IPython.utils import py3compat
113 from IPython.utils import py3compat
114 from IPython.utils.terminal import get_terminal_size
114 from IPython.utils.terminal import get_terminal_size
115
115
116 import IPython.utils.colorable as colorable
116 import IPython.utils.colorable as colorable
117
117
118 # Globals
118 # Globals
119 # amount of space to put line numbers before verbose tracebacks
119 # amount of space to put line numbers before verbose tracebacks
120 INDENT_SIZE = 8
120 INDENT_SIZE = 8
121
121
122 # Default color scheme. This is used, for example, by the traceback
122 # Default color scheme. This is used, for example, by the traceback
123 # formatter. When running in an actual IPython instance, the user's rc.colors
123 # formatter. When running in an actual IPython instance, the user's rc.colors
124 # value is used, but having a module global makes this functionality available
124 # value is used, but having a module global makes this functionality available
125 # to users of ultratb who are NOT running inside ipython.
125 # to users of ultratb who are NOT running inside ipython.
126 DEFAULT_SCHEME = 'NoColor'
126 DEFAULT_SCHEME = 'NoColor'
127 FAST_THRESHOLD = 10_000
127 FAST_THRESHOLD = 10_000
128
128
129 # ---------------------------------------------------------------------------
129 # ---------------------------------------------------------------------------
130 # Code begins
130 # Code begins
131
131
132 # Helper function -- largely belongs to VerboseTB, but we need the same
132 # Helper function -- largely belongs to VerboseTB, but we need the same
133 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
133 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
134 # can be recognized properly by ipython.el's py-traceback-line-re
134 # can be recognized properly by ipython.el's py-traceback-line-re
135 # (SyntaxErrors have to be treated specially because they have no traceback)
135 # (SyntaxErrors have to be treated specially because they have no traceback)
136
136
137
137
138 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
138 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
139 """
139 """
140 Format tracebacks lines with pointing arrow, leading numbers...
140 Format tracebacks lines with pointing arrow, leading numbers...
141
141
142 Parameters
142 Parameters
143 ----------
143 ----------
144 lines : list[Line]
144 lines : list[Line]
145 Colors
145 Colors
146 ColorScheme used.
146 ColorScheme used.
147 lvals : str
147 lvals : str
148 Values of local variables, already colored, to inject just after the error line.
148 Values of local variables, already colored, to inject just after the error line.
149 """
149 """
150 numbers_width = INDENT_SIZE - 1
150 numbers_width = INDENT_SIZE - 1
151 res = []
151 res = []
152
152
153 for stack_line in lines:
153 for stack_line in lines:
154 if stack_line is stack_data.LINE_GAP:
154 if stack_line is stack_data.LINE_GAP:
155 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
155 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
156 continue
156 continue
157
157
158 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
158 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
159 lineno = stack_line.lineno
159 lineno = stack_line.lineno
160 if stack_line.is_current:
160 if stack_line.is_current:
161 # This is the line with the error
161 # This is the line with the error
162 pad = numbers_width - len(str(lineno))
162 pad = numbers_width - len(str(lineno))
163 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
163 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
164 start_color = Colors.linenoEm
164 start_color = Colors.linenoEm
165 else:
165 else:
166 num = '%*s' % (numbers_width, lineno)
166 num = '%*s' % (numbers_width, lineno)
167 start_color = Colors.lineno
167 start_color = Colors.lineno
168
168
169 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
169 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
170
170
171 res.append(line)
171 res.append(line)
172 if lvals and stack_line.is_current:
172 if lvals and stack_line.is_current:
173 res.append(lvals + '\n')
173 res.append(lvals + '\n')
174 return res
174 return res
175
175
176 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
176 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
177 """
177 """
178 Format tracebacks lines with pointing arrow, leading numbers...
178 Format tracebacks lines with pointing arrow, leading numbers...
179
179
180 Parameters
180 Parameters
181 ==========
181 ==========
182
182
183 lnum: int
183 lnum: int
184 number of the target line of code.
184 number of the target line of code.
185 index: int
185 index: int
186 which line in the list should be highlighted.
186 which line in the list should be highlighted.
187 lines: list[string]
187 lines: list[string]
188 Colors:
188 Colors:
189 ColorScheme used.
189 ColorScheme used.
190 lvals: bytes
190 lvals: bytes
191 Values of local variables, already colored, to inject just after the error line.
191 Values of local variables, already colored, to inject just after the error line.
192 _line_format: f (str) -> (str, bool)
192 _line_format: f (str) -> (str, bool)
193 return (colorized version of str, failure to do so)
193 return (colorized version of str, failure to do so)
194 """
194 """
195 numbers_width = INDENT_SIZE - 1
195 numbers_width = INDENT_SIZE - 1
196 res = []
196 res = []
197
197
198 for i, line in enumerate(lines, lnum - index):
198 for i, line in enumerate(lines, lnum - index):
199 line = py3compat.cast_unicode(line)
199 line = py3compat.cast_unicode(line)
200
200
201 new_line, err = _line_format(line, "str")
201 new_line, err = _line_format(line, "str")
202 if not err:
202 if not err:
203 line = new_line
203 line = new_line
204
204
205 if i == lnum:
205 if i == lnum:
206 # This is the line with the error
206 # This is the line with the error
207 pad = numbers_width - len(str(i))
207 pad = numbers_width - len(str(i))
208 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
208 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
209 line = "%s%s%s %s%s" % (
209 line = "%s%s%s %s%s" % (
210 Colors.linenoEm,
210 Colors.linenoEm,
211 num,
211 num,
212 Colors.line,
212 Colors.line,
213 line,
213 line,
214 Colors.Normal,
214 Colors.Normal,
215 )
215 )
216 else:
216 else:
217 num = "%*s" % (numbers_width, i)
217 num = "%*s" % (numbers_width, i)
218 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
218 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
219
219
220 res.append(line)
220 res.append(line)
221 if lvals and i == lnum:
221 if lvals and i == lnum:
222 res.append(lvals + "\n")
222 res.append(lvals + "\n")
223 return res
223 return res
224
224
225
225
226 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
226 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
227 """
227 """
228 Format filename lines with custom formatting from caching compiler or `File *.py` by default
228 Format filename lines with custom formatting from caching compiler or `File *.py` by default
229
229
230 Parameters
230 Parameters
231 ----------
231 ----------
232 file : str
232 file : str
233 ColorFilename
233 ColorFilename
234 ColorScheme's filename coloring to be used.
234 ColorScheme's filename coloring to be used.
235 ColorNormal
235 ColorNormal
236 ColorScheme's normal coloring to be used.
236 ColorScheme's normal coloring to be used.
237 """
237 """
238 ipinst = get_ipython()
238 ipinst = get_ipython()
239 if (
239 if (
240 ipinst is not None
240 ipinst is not None
241 and (data := ipinst.compile.format_code_name(file)) is not None
241 and (data := ipinst.compile.format_code_name(file)) is not None
242 ):
242 ):
243 label, name = data
243 label, name = data
244 if lineno is None:
244 if lineno is None:
245 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
245 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
246 else:
246 else:
247 tpl_link = (
247 tpl_link = (
248 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
248 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
249 )
249 )
250 else:
250 else:
251 label = "File"
251 label = "File"
252 name = util_path.compress_user(
252 name = util_path.compress_user(
253 py3compat.cast_unicode(file, util_path.fs_encoding)
253 py3compat.cast_unicode(file, util_path.fs_encoding)
254 )
254 )
255 if lineno is None:
255 if lineno is None:
256 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
256 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
257 else:
257 else:
258 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
258 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
259 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
259 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
260
260
261 return tpl_link.format(label=label, name=name, lineno=lineno)
261 return tpl_link.format(label=label, name=name, lineno=lineno)
262
262
263 #---------------------------------------------------------------------------
263 #---------------------------------------------------------------------------
264 # Module classes
264 # Module classes
265 class TBTools(colorable.Colorable):
265 class TBTools(colorable.Colorable):
266 """Basic tools used by all traceback printer classes."""
266 """Basic tools used by all traceback printer classes."""
267
267
268 # Number of frames to skip when reporting tracebacks
268 # Number of frames to skip when reporting tracebacks
269 tb_offset = 0
269 tb_offset = 0
270
270
271 def __init__(
271 def __init__(
272 self,
272 self,
273 color_scheme="NoColor",
273 color_scheme="NoColor",
274 call_pdb=False,
274 call_pdb=False,
275 ostream=None,
275 ostream=None,
276 parent=None,
276 parent=None,
277 config=None,
277 config=None,
278 *,
278 *,
279 debugger_cls=None,
279 debugger_cls=None,
280 ):
280 ):
281 # Whether to call the interactive pdb debugger after printing
281 # Whether to call the interactive pdb debugger after printing
282 # tracebacks or not
282 # tracebacks or not
283 super(TBTools, self).__init__(parent=parent, config=config)
283 super(TBTools, self).__init__(parent=parent, config=config)
284 self.call_pdb = call_pdb
284 self.call_pdb = call_pdb
285
285
286 # Output stream to write to. Note that we store the original value in
286 # Output stream to write to. Note that we store the original value in
287 # a private attribute and then make the public ostream a property, so
287 # a private attribute and then make the public ostream a property, so
288 # that we can delay accessing sys.stdout until runtime. The way
288 # that we can delay accessing sys.stdout until runtime. The way
289 # things are written now, the sys.stdout object is dynamically managed
289 # things are written now, the sys.stdout object is dynamically managed
290 # so a reference to it should NEVER be stored statically. This
290 # so a reference to it should NEVER be stored statically. This
291 # property approach confines this detail to a single location, and all
291 # property approach confines this detail to a single location, and all
292 # subclasses can simply access self.ostream for writing.
292 # subclasses can simply access self.ostream for writing.
293 self._ostream = ostream
293 self._ostream = ostream
294
294
295 # Create color table
295 # Create color table
296 self.color_scheme_table = exception_colors()
296 self.color_scheme_table = exception_colors()
297
297
298 self.set_colors(color_scheme)
298 self.set_colors(color_scheme)
299 self.old_scheme = color_scheme # save initial value for toggles
299 self.old_scheme = color_scheme # save initial value for toggles
300 self.debugger_cls = debugger_cls or debugger.Pdb
300 self.debugger_cls = debugger_cls or debugger.Pdb
301
301
302 if call_pdb:
302 if call_pdb:
303 self.pdb = self.debugger_cls()
303 self.pdb = self.debugger_cls()
304 else:
304 else:
305 self.pdb = None
305 self.pdb = None
306
306
307 def _get_ostream(self):
307 def _get_ostream(self):
308 """Output stream that exceptions are written to.
308 """Output stream that exceptions are written to.
309
309
310 Valid values are:
310 Valid values are:
311
311
312 - None: the default, which means that IPython will dynamically resolve
312 - None: the default, which means that IPython will dynamically resolve
313 to sys.stdout. This ensures compatibility with most tools, including
313 to sys.stdout. This ensures compatibility with most tools, including
314 Windows (where plain stdout doesn't recognize ANSI escapes).
314 Windows (where plain stdout doesn't recognize ANSI escapes).
315
315
316 - Any object with 'write' and 'flush' attributes.
316 - Any object with 'write' and 'flush' attributes.
317 """
317 """
318 return sys.stdout if self._ostream is None else self._ostream
318 return sys.stdout if self._ostream is None else self._ostream
319
319
320 def _set_ostream(self, val):
320 def _set_ostream(self, val):
321 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
321 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
322 self._ostream = val
322 self._ostream = val
323
323
324 ostream = property(_get_ostream, _set_ostream)
324 ostream = property(_get_ostream, _set_ostream)
325
325
326 @staticmethod
326 @staticmethod
327 def _get_chained_exception(exception_value):
327 def _get_chained_exception(exception_value):
328 cause = getattr(exception_value, "__cause__", None)
328 cause = getattr(exception_value, "__cause__", None)
329 if cause:
329 if cause:
330 return cause
330 return cause
331 if getattr(exception_value, "__suppress_context__", False):
331 if getattr(exception_value, "__suppress_context__", False):
332 return None
332 return None
333 return getattr(exception_value, "__context__", None)
333 return getattr(exception_value, "__context__", None)
334
334
335 def get_parts_of_chained_exception(
335 def get_parts_of_chained_exception(
336 self, evalue
336 self, evalue
337 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
337 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
338 chained_evalue = self._get_chained_exception(evalue)
338 chained_evalue = self._get_chained_exception(evalue)
339
339
340 if chained_evalue:
340 if chained_evalue:
341 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
341 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
342 return None
342 return None
343
343
344 def prepare_chained_exception_message(self, cause) -> List[Any]:
344 def prepare_chained_exception_message(self, cause) -> List[Any]:
345 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
345 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
346 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
346 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
347
347
348 if cause:
348 if cause:
349 message = [[direct_cause]]
349 message = [[direct_cause]]
350 else:
350 else:
351 message = [[exception_during_handling]]
351 message = [[exception_during_handling]]
352 return message
352 return message
353
353
354 @property
354 @property
355 def has_colors(self) -> bool:
355 def has_colors(self) -> bool:
356 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
356 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
357
357
358 def set_colors(self, *args, **kw):
358 def set_colors(self, *args, **kw):
359 """Shorthand access to the color table scheme selector method."""
359 """Shorthand access to the color table scheme selector method."""
360
360
361 # Set own color table
361 # Set own color table
362 self.color_scheme_table.set_active_scheme(*args, **kw)
362 self.color_scheme_table.set_active_scheme(*args, **kw)
363 # for convenience, set Colors to the active scheme
363 # for convenience, set Colors to the active scheme
364 self.Colors = self.color_scheme_table.active_colors
364 self.Colors = self.color_scheme_table.active_colors
365 # Also set colors of debugger
365 # Also set colors of debugger
366 if hasattr(self, 'pdb') and self.pdb is not None:
366 if hasattr(self, 'pdb') and self.pdb is not None:
367 self.pdb.set_colors(*args, **kw)
367 self.pdb.set_colors(*args, **kw)
368
368
369 def color_toggle(self):
369 def color_toggle(self):
370 """Toggle between the currently active color scheme and NoColor."""
370 """Toggle between the currently active color scheme and NoColor."""
371
371
372 if self.color_scheme_table.active_scheme_name == 'NoColor':
372 if self.color_scheme_table.active_scheme_name == 'NoColor':
373 self.color_scheme_table.set_active_scheme(self.old_scheme)
373 self.color_scheme_table.set_active_scheme(self.old_scheme)
374 self.Colors = self.color_scheme_table.active_colors
374 self.Colors = self.color_scheme_table.active_colors
375 else:
375 else:
376 self.old_scheme = self.color_scheme_table.active_scheme_name
376 self.old_scheme = self.color_scheme_table.active_scheme_name
377 self.color_scheme_table.set_active_scheme('NoColor')
377 self.color_scheme_table.set_active_scheme('NoColor')
378 self.Colors = self.color_scheme_table.active_colors
378 self.Colors = self.color_scheme_table.active_colors
379
379
380 def stb2text(self, stb):
380 def stb2text(self, stb):
381 """Convert a structured traceback (a list) to a string."""
381 """Convert a structured traceback (a list) to a string."""
382 return '\n'.join(stb)
382 return '\n'.join(stb)
383
383
384 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
384 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
385 """Return formatted traceback.
385 """Return formatted traceback.
386
386
387 Subclasses may override this if they add extra arguments.
387 Subclasses may override this if they add extra arguments.
388 """
388 """
389 tb_list = self.structured_traceback(etype, value, tb,
389 tb_list = self.structured_traceback(etype, value, tb,
390 tb_offset, context)
390 tb_offset, context)
391 return self.stb2text(tb_list)
391 return self.stb2text(tb_list)
392
392
393 def structured_traceback(
393 def structured_traceback(
394 self, etype, evalue, tb, tb_offset: Optional[int] = None, context=5, mode=None
394 self,
395 etype: type,
396 evalue: Optional[BaseException],
397 etb: Optional[TracebackType] = None,
398 tb_offset: Optional[int] = None,
399 context=5,
395 ):
400 ):
396 """Return a list of traceback frames.
401 """Return a list of traceback frames.
397
402
398 Must be implemented by each class.
403 Must be implemented by each class.
399 """
404 """
400 raise NotImplementedError()
405 raise NotImplementedError()
401
406
402
407
403 #---------------------------------------------------------------------------
408 #---------------------------------------------------------------------------
404 class ListTB(TBTools):
409 class ListTB(TBTools):
405 """Print traceback information from a traceback list, with optional color.
410 """Print traceback information from a traceback list, with optional color.
406
411
407 Calling requires 3 arguments: (etype, evalue, elist)
412 Calling requires 3 arguments: (etype, evalue, elist)
408 as would be obtained by::
413 as would be obtained by::
409
414
410 etype, evalue, tb = sys.exc_info()
415 etype, evalue, tb = sys.exc_info()
411 if tb:
416 if tb:
412 elist = traceback.extract_tb(tb)
417 elist = traceback.extract_tb(tb)
413 else:
418 else:
414 elist = None
419 elist = None
415
420
416 It can thus be used by programs which need to process the traceback before
421 It can thus be used by programs which need to process the traceback before
417 printing (such as console replacements based on the code module from the
422 printing (such as console replacements based on the code module from the
418 standard library).
423 standard library).
419
424
420 Because they are meant to be called without a full traceback (only a
425 Because they are meant to be called without a full traceback (only a
421 list), instances of this class can't call the interactive pdb debugger."""
426 list), instances of this class can't call the interactive pdb debugger."""
422
427
423
428
424 def __call__(self, etype, value, elist):
429 def __call__(self, etype, value, elist):
425 self.ostream.flush()
430 self.ostream.flush()
426 self.ostream.write(self.text(etype, value, elist))
431 self.ostream.write(self.text(etype, value, elist))
427 self.ostream.write('\n')
432 self.ostream.write('\n')
428
433
429 def _extract_tb(self, tb):
434 def _extract_tb(self, tb):
430 if tb:
435 if tb:
431 return traceback.extract_tb(tb)
436 return traceback.extract_tb(tb)
432 else:
437 else:
433 return None
438 return None
434
439
435 def structured_traceback(
440 def structured_traceback(
436 self,
441 self,
437 etype: type,
442 etype: type,
438 evalue: BaseException,
443 evalue: Optional[BaseException],
439 etb: Optional[TracebackType] = None,
444 etb: Optional[TracebackType] = None,
440 tb_offset: Optional[int] = None,
445 tb_offset: Optional[int] = None,
441 context=5,
446 context=5,
442 ):
447 ):
443 """Return a color formatted string with the traceback info.
448 """Return a color formatted string with the traceback info.
444
449
445 Parameters
450 Parameters
446 ----------
451 ----------
447 etype : exception type
452 etype : exception type
448 Type of the exception raised.
453 Type of the exception raised.
449 evalue : object
454 evalue : object
450 Data stored in the exception
455 Data stored in the exception
451 etb : list | TracebackType | None
456 etb : list | TracebackType | None
452 If list: List of frames, see class docstring for details.
457 If list: List of frames, see class docstring for details.
453 If Traceback: Traceback of the exception.
458 If Traceback: Traceback of the exception.
454 tb_offset : int, optional
459 tb_offset : int, optional
455 Number of frames in the traceback to skip. If not given, the
460 Number of frames in the traceback to skip. If not given, the
456 instance evalue is used (set in constructor).
461 instance evalue is used (set in constructor).
457 context : int, optional
462 context : int, optional
458 Number of lines of context information to print.
463 Number of lines of context information to print.
459
464
460 Returns
465 Returns
461 -------
466 -------
462 String with formatted exception.
467 String with formatted exception.
463 """
468 """
464 # This is a workaround to get chained_exc_ids in recursive calls
469 # This is a workaround to get chained_exc_ids in recursive calls
465 # etb should not be a tuple if structured_traceback is not recursive
470 # etb should not be a tuple if structured_traceback is not recursive
466 if isinstance(etb, tuple):
471 if isinstance(etb, tuple):
467 etb, chained_exc_ids = etb
472 etb, chained_exc_ids = etb
468 else:
473 else:
469 chained_exc_ids = set()
474 chained_exc_ids = set()
470
475
471 if isinstance(etb, list):
476 if isinstance(etb, list):
472 elist = etb
477 elist = etb
473 elif etb is not None:
478 elif etb is not None:
474 elist = self._extract_tb(etb)
479 elist = self._extract_tb(etb)
475 else:
480 else:
476 elist = []
481 elist = []
477 tb_offset = self.tb_offset if tb_offset is None else tb_offset
482 tb_offset = self.tb_offset if tb_offset is None else tb_offset
478 assert isinstance(tb_offset, int)
483 assert isinstance(tb_offset, int)
479 Colors = self.Colors
484 Colors = self.Colors
480 out_list = []
485 out_list = []
481 if elist:
486 if elist:
482
487
483 if tb_offset and len(elist) > tb_offset:
488 if tb_offset and len(elist) > tb_offset:
484 elist = elist[tb_offset:]
489 elist = elist[tb_offset:]
485
490
486 out_list.append('Traceback %s(most recent call last)%s:' %
491 out_list.append('Traceback %s(most recent call last)%s:' %
487 (Colors.normalEm, Colors.Normal) + '\n')
492 (Colors.normalEm, Colors.Normal) + '\n')
488 out_list.extend(self._format_list(elist))
493 out_list.extend(self._format_list(elist))
489 # The exception info should be a single entry in the list.
494 # The exception info should be a single entry in the list.
490 lines = ''.join(self._format_exception_only(etype, evalue))
495 lines = ''.join(self._format_exception_only(etype, evalue))
491 out_list.append(lines)
496 out_list.append(lines)
492
497
493 exception = self.get_parts_of_chained_exception(evalue)
498 exception = self.get_parts_of_chained_exception(evalue)
494
499
495 if exception and not id(exception[1]) in chained_exc_ids:
500 if exception and not id(exception[1]) in chained_exc_ids:
496 chained_exception_message = self.prepare_chained_exception_message(
501 chained_exception_message = (
497 evalue.__cause__)[0]
502 self.prepare_chained_exception_message(evalue.__cause__)[0]
503 if evalue is not None
504 else ""
505 )
498 etype, evalue, etb = exception
506 etype, evalue, etb = exception
499 # Trace exception to avoid infinite 'cause' loop
507 # Trace exception to avoid infinite 'cause' loop
500 chained_exc_ids.add(id(exception[1]))
508 chained_exc_ids.add(id(exception[1]))
501 chained_exceptions_tb_offset = 0
509 chained_exceptions_tb_offset = 0
502 out_list = (
510 out_list = (
503 self.structured_traceback(
511 self.structured_traceback(
504 etype, evalue, (etb, chained_exc_ids),
512 etype, evalue, (etb, chained_exc_ids),
505 chained_exceptions_tb_offset, context)
513 chained_exceptions_tb_offset, context)
506 + chained_exception_message
514 + chained_exception_message
507 + out_list)
515 + out_list)
508
516
509 return out_list
517 return out_list
510
518
511 def _format_list(self, extracted_list):
519 def _format_list(self, extracted_list):
512 """Format a list of traceback entry tuples for printing.
520 """Format a list of traceback entry tuples for printing.
513
521
514 Given a list of tuples as returned by extract_tb() or
522 Given a list of tuples as returned by extract_tb() or
515 extract_stack(), return a list of strings ready for printing.
523 extract_stack(), return a list of strings ready for printing.
516 Each string in the resulting list corresponds to the item with the
524 Each string in the resulting list corresponds to the item with the
517 same index in the argument list. Each string ends in a newline;
525 same index in the argument list. Each string ends in a newline;
518 the strings may contain internal newlines as well, for those items
526 the strings may contain internal newlines as well, for those items
519 whose source text line is not None.
527 whose source text line is not None.
520
528
521 Lifted almost verbatim from traceback.py
529 Lifted almost verbatim from traceback.py
522 """
530 """
523
531
524 Colors = self.Colors
532 Colors = self.Colors
525 list = []
533 list = []
526 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
534 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
527 normalCol, nameCol, fileCol, lineCol = (
535 normalCol, nameCol, fileCol, lineCol = (
528 # Emphasize the last entry
536 # Emphasize the last entry
529 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
537 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
530 if ind == len(extracted_list) - 1
538 if ind == len(extracted_list) - 1
531 else (Colors.Normal, Colors.name, Colors.filename, "")
539 else (Colors.Normal, Colors.name, Colors.filename, "")
532 )
540 )
533
541
534 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
542 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
535 item = f"{normalCol} {fns}"
543 item = f"{normalCol} {fns}"
536
544
537 if name != "<module>":
545 if name != "<module>":
538 item += f" in {nameCol}{name}{normalCol}\n"
546 item += f" in {nameCol}{name}{normalCol}\n"
539 else:
547 else:
540 item += "\n"
548 item += "\n"
541 if line:
549 if line:
542 item += f"{lineCol} {line.strip()}{normalCol}\n"
550 item += f"{lineCol} {line.strip()}{normalCol}\n"
543 list.append(item)
551 list.append(item)
544
552
545 return list
553 return list
546
554
547 def _format_exception_only(self, etype, value):
555 def _format_exception_only(self, etype, value):
548 """Format the exception part of a traceback.
556 """Format the exception part of a traceback.
549
557
550 The arguments are the exception type and value such as given by
558 The arguments are the exception type and value such as given by
551 sys.exc_info()[:2]. The return value is a list of strings, each ending
559 sys.exc_info()[:2]. The return value is a list of strings, each ending
552 in a newline. Normally, the list contains a single string; however,
560 in a newline. Normally, the list contains a single string; however,
553 for SyntaxError exceptions, it contains several lines that (when
561 for SyntaxError exceptions, it contains several lines that (when
554 printed) display detailed information about where the syntax error
562 printed) display detailed information about where the syntax error
555 occurred. The message indicating which exception occurred is the
563 occurred. The message indicating which exception occurred is the
556 always last string in the list.
564 always last string in the list.
557
565
558 Also lifted nearly verbatim from traceback.py
566 Also lifted nearly verbatim from traceback.py
559 """
567 """
560 have_filedata = False
568 have_filedata = False
561 Colors = self.Colors
569 Colors = self.Colors
562 list = []
570 list = []
563 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
571 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
564 if value is None:
572 if value is None:
565 # Not sure if this can still happen in Python 2.6 and above
573 # Not sure if this can still happen in Python 2.6 and above
566 list.append(stype + '\n')
574 list.append(stype + '\n')
567 else:
575 else:
568 if issubclass(etype, SyntaxError):
576 if issubclass(etype, SyntaxError):
569 have_filedata = True
577 have_filedata = True
570 if not value.filename: value.filename = "<string>"
578 if not value.filename: value.filename = "<string>"
571 if value.lineno:
579 if value.lineno:
572 lineno = value.lineno
580 lineno = value.lineno
573 textline = linecache.getline(value.filename, value.lineno)
581 textline = linecache.getline(value.filename, value.lineno)
574 else:
582 else:
575 lineno = "unknown"
583 lineno = "unknown"
576 textline = ""
584 textline = ""
577 list.append(
585 list.append(
578 "%s %s%s\n"
586 "%s %s%s\n"
579 % (
587 % (
580 Colors.normalEm,
588 Colors.normalEm,
581 _format_filename(
589 _format_filename(
582 value.filename,
590 value.filename,
583 Colors.filenameEm,
591 Colors.filenameEm,
584 Colors.normalEm,
592 Colors.normalEm,
585 lineno=(None if lineno == "unknown" else lineno),
593 lineno=(None if lineno == "unknown" else lineno),
586 ),
594 ),
587 Colors.Normal,
595 Colors.Normal,
588 )
596 )
589 )
597 )
590 if textline == "":
598 if textline == "":
591 textline = py3compat.cast_unicode(value.text, "utf-8")
599 textline = py3compat.cast_unicode(value.text, "utf-8")
592
600
593 if textline is not None:
601 if textline is not None:
594 i = 0
602 i = 0
595 while i < len(textline) and textline[i].isspace():
603 while i < len(textline) and textline[i].isspace():
596 i += 1
604 i += 1
597 list.append('%s %s%s\n' % (Colors.line,
605 list.append('%s %s%s\n' % (Colors.line,
598 textline.strip(),
606 textline.strip(),
599 Colors.Normal))
607 Colors.Normal))
600 if value.offset is not None:
608 if value.offset is not None:
601 s = ' '
609 s = ' '
602 for c in textline[i:value.offset - 1]:
610 for c in textline[i:value.offset - 1]:
603 if c.isspace():
611 if c.isspace():
604 s += c
612 s += c
605 else:
613 else:
606 s += ' '
614 s += ' '
607 list.append('%s%s^%s\n' % (Colors.caret, s,
615 list.append('%s%s^%s\n' % (Colors.caret, s,
608 Colors.Normal))
616 Colors.Normal))
609
617
610 try:
618 try:
611 s = value.msg
619 s = value.msg
612 except Exception:
620 except Exception:
613 s = self._some_str(value)
621 s = self._some_str(value)
614 if s:
622 if s:
615 list.append('%s%s:%s %s\n' % (stype, Colors.excName,
623 list.append('%s%s:%s %s\n' % (stype, Colors.excName,
616 Colors.Normal, s))
624 Colors.Normal, s))
617 else:
625 else:
618 list.append('%s\n' % stype)
626 list.append('%s\n' % stype)
619
627
620 # sync with user hooks
628 # sync with user hooks
621 if have_filedata:
629 if have_filedata:
622 ipinst = get_ipython()
630 ipinst = get_ipython()
623 if ipinst is not None:
631 if ipinst is not None:
624 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
632 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
625
633
626 return list
634 return list
627
635
628 def get_exception_only(self, etype, value):
636 def get_exception_only(self, etype, value):
629 """Only print the exception type and message, without a traceback.
637 """Only print the exception type and message, without a traceback.
630
638
631 Parameters
639 Parameters
632 ----------
640 ----------
633 etype : exception type
641 etype : exception type
634 value : exception value
642 value : exception value
635 """
643 """
636 return ListTB.structured_traceback(self, etype, value)
644 return ListTB.structured_traceback(self, etype, value)
637
645
638 def show_exception_only(self, etype, evalue):
646 def show_exception_only(self, etype, evalue):
639 """Only print the exception type and message, without a traceback.
647 """Only print the exception type and message, without a traceback.
640
648
641 Parameters
649 Parameters
642 ----------
650 ----------
643 etype : exception type
651 etype : exception type
644 evalue : exception value
652 evalue : exception value
645 """
653 """
646 # This method needs to use __call__ from *this* class, not the one from
654 # This method needs to use __call__ from *this* class, not the one from
647 # a subclass whose signature or behavior may be different
655 # a subclass whose signature or behavior may be different
648 ostream = self.ostream
656 ostream = self.ostream
649 ostream.flush()
657 ostream.flush()
650 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
658 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
651 ostream.flush()
659 ostream.flush()
652
660
653 def _some_str(self, value):
661 def _some_str(self, value):
654 # Lifted from traceback.py
662 # Lifted from traceback.py
655 try:
663 try:
656 return py3compat.cast_unicode(str(value))
664 return py3compat.cast_unicode(str(value))
657 except:
665 except:
658 return u'<unprintable %s object>' % type(value).__name__
666 return u'<unprintable %s object>' % type(value).__name__
659
667
660
668
661 class FrameInfo:
669 class FrameInfo:
662 """
670 """
663 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
671 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
664 really long frames.
672 really long frames.
665 """
673 """
666
674
667 description: Optional[str]
675 description: Optional[str]
668 filename: str
676 filename: str
669 lineno: int
677 lineno: int
670
678
671 @classmethod
679 @classmethod
672 def _from_stack_data_FrameInfo(cls, frame_info):
680 def _from_stack_data_FrameInfo(cls, frame_info):
673 return cls(
681 return cls(
674 getattr(frame_info, "description", None),
682 getattr(frame_info, "description", None),
675 getattr(frame_info, "filename", None),
683 getattr(frame_info, "filename", None),
676 getattr(frame_info, "lineno", None),
684 getattr(frame_info, "lineno", None),
677 getattr(frame_info, "frame", None),
685 getattr(frame_info, "frame", None),
678 getattr(frame_info, "code", None),
686 getattr(frame_info, "code", None),
679 sd=frame_info,
687 sd=frame_info,
680 )
688 )
681
689
682 def __init__(self, description, filename, lineno, frame, code, sd=None):
690 def __init__(self, description, filename, lineno, frame, code, sd=None):
683 self.description = description
691 self.description = description
684 self.filename = filename
692 self.filename = filename
685 self.lineno = lineno
693 self.lineno = lineno
686 self.frame = frame
694 self.frame = frame
687 self.code = code
695 self.code = code
688 self._sd = sd
696 self._sd = sd
689
697
690 # self.lines = []
698 # self.lines = []
691 if sd is None:
699 if sd is None:
692 ix = inspect.getsourcelines(frame)
700 ix = inspect.getsourcelines(frame)
693 self.raw_lines = ix[0]
701 self.raw_lines = ix[0]
694
702
695 @property
703 @property
696 def variables_in_executing_piece(self):
704 def variables_in_executing_piece(self):
697 if self._sd:
705 if self._sd:
698 return self._sd.variables_in_executing_piece
706 return self._sd.variables_in_executing_piece
699 else:
707 else:
700 return []
708 return []
701
709
702 @property
710 @property
703 def lines(self):
711 def lines(self):
704 return self._sd.lines
712 return self._sd.lines
705
713
706 @property
714 @property
707 def executing(self):
715 def executing(self):
708 if self._sd:
716 if self._sd:
709 return self._sd.executing
717 return self._sd.executing
710 else:
718 else:
711 return None
719 return None
712
720
713
721
714 # ----------------------------------------------------------------------------
722 # ----------------------------------------------------------------------------
715 class VerboseTB(TBTools):
723 class VerboseTB(TBTools):
716 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
724 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
717 of HTML. Requires inspect and pydoc. Crazy, man.
725 of HTML. Requires inspect and pydoc. Crazy, man.
718
726
719 Modified version which optionally strips the topmost entries from the
727 Modified version which optionally strips the topmost entries from the
720 traceback, to be used with alternate interpreters (because their own code
728 traceback, to be used with alternate interpreters (because their own code
721 would appear in the traceback)."""
729 would appear in the traceback)."""
722
730
723 _tb_highlight = "bg:ansiyellow"
731 _tb_highlight = "bg:ansiyellow"
724
732
725 def __init__(
733 def __init__(
726 self,
734 self,
727 color_scheme: str = "Linux",
735 color_scheme: str = "Linux",
728 call_pdb: bool = False,
736 call_pdb: bool = False,
729 ostream=None,
737 ostream=None,
730 tb_offset: int = 0,
738 tb_offset: int = 0,
731 long_header: bool = False,
739 long_header: bool = False,
732 include_vars: bool = True,
740 include_vars: bool = True,
733 check_cache=None,
741 check_cache=None,
734 debugger_cls=None,
742 debugger_cls=None,
735 parent=None,
743 parent=None,
736 config=None,
744 config=None,
737 ):
745 ):
738 """Specify traceback offset, headers and color scheme.
746 """Specify traceback offset, headers and color scheme.
739
747
740 Define how many frames to drop from the tracebacks. Calling it with
748 Define how many frames to drop from the tracebacks. Calling it with
741 tb_offset=1 allows use of this handler in interpreters which will have
749 tb_offset=1 allows use of this handler in interpreters which will have
742 their own code at the top of the traceback (VerboseTB will first
750 their own code at the top of the traceback (VerboseTB will first
743 remove that frame before printing the traceback info)."""
751 remove that frame before printing the traceback info)."""
744 TBTools.__init__(
752 TBTools.__init__(
745 self,
753 self,
746 color_scheme=color_scheme,
754 color_scheme=color_scheme,
747 call_pdb=call_pdb,
755 call_pdb=call_pdb,
748 ostream=ostream,
756 ostream=ostream,
749 parent=parent,
757 parent=parent,
750 config=config,
758 config=config,
751 debugger_cls=debugger_cls,
759 debugger_cls=debugger_cls,
752 )
760 )
753 self.tb_offset = tb_offset
761 self.tb_offset = tb_offset
754 self.long_header = long_header
762 self.long_header = long_header
755 self.include_vars = include_vars
763 self.include_vars = include_vars
756 # By default we use linecache.checkcache, but the user can provide a
764 # By default we use linecache.checkcache, but the user can provide a
757 # different check_cache implementation. This was formerly used by the
765 # different check_cache implementation. This was formerly used by the
758 # IPython kernel for interactive code, but is no longer necessary.
766 # IPython kernel for interactive code, but is no longer necessary.
759 if check_cache is None:
767 if check_cache is None:
760 check_cache = linecache.checkcache
768 check_cache = linecache.checkcache
761 self.check_cache = check_cache
769 self.check_cache = check_cache
762
770
763 self.skip_hidden = True
771 self.skip_hidden = True
764
772
765 def format_record(self, frame_info: FrameInfo):
773 def format_record(self, frame_info: FrameInfo):
766 """Format a single stack frame"""
774 """Format a single stack frame"""
767 assert isinstance(frame_info, FrameInfo)
775 assert isinstance(frame_info, FrameInfo)
768 Colors = self.Colors # just a shorthand + quicker name lookup
776 Colors = self.Colors # just a shorthand + quicker name lookup
769 ColorsNormal = Colors.Normal # used a lot
777 ColorsNormal = Colors.Normal # used a lot
770
778
771 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
779 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
772 return ' %s[... skipping similar frames: %s]%s\n' % (
780 return ' %s[... skipping similar frames: %s]%s\n' % (
773 Colors.excName, frame_info.description, ColorsNormal)
781 Colors.excName, frame_info.description, ColorsNormal)
774
782
775 indent = " " * INDENT_SIZE
783 indent = " " * INDENT_SIZE
776 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
784 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
777 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
785 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
778 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
786 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
779 Colors.vName,
787 Colors.vName,
780 Colors.valEm,
788 Colors.valEm,
781 ColorsNormal,
789 ColorsNormal,
782 )
790 )
783 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
791 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
784
792
785 link = _format_filename(
793 link = _format_filename(
786 frame_info.filename,
794 frame_info.filename,
787 Colors.filenameEm,
795 Colors.filenameEm,
788 ColorsNormal,
796 ColorsNormal,
789 lineno=frame_info.lineno,
797 lineno=frame_info.lineno,
790 )
798 )
791 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
799 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
792 if frame_info.executing is not None:
800 if frame_info.executing is not None:
793 func = frame_info.executing.code_qualname()
801 func = frame_info.executing.code_qualname()
794 else:
802 else:
795 func = "?"
803 func = "?"
796 if func == "<module>":
804 if func == "<module>":
797 call = ""
805 call = ""
798 else:
806 else:
799 # Decide whether to include variable details or not
807 # Decide whether to include variable details or not
800 var_repr = eqrepr if self.include_vars else nullrepr
808 var_repr = eqrepr if self.include_vars else nullrepr
801 try:
809 try:
802 scope = inspect.formatargvalues(
810 scope = inspect.formatargvalues(
803 args, varargs, varkw, locals_, formatvalue=var_repr
811 args, varargs, varkw, locals_, formatvalue=var_repr
804 )
812 )
805 call = tpl_call.format(file=func, scope=scope)
813 call = tpl_call.format(file=func, scope=scope)
806 except KeyError:
814 except KeyError:
807 # This happens in situations like errors inside generator
815 # This happens in situations like errors inside generator
808 # expressions, where local variables are listed in the
816 # expressions, where local variables are listed in the
809 # line, but can't be extracted from the frame. I'm not
817 # line, but can't be extracted from the frame. I'm not
810 # 100% sure this isn't actually a bug in inspect itself,
818 # 100% sure this isn't actually a bug in inspect itself,
811 # but since there's no info for us to compute with, the
819 # but since there's no info for us to compute with, the
812 # best we can do is report the failure and move on. Here
820 # best we can do is report the failure and move on. Here
813 # we must *not* call any traceback construction again,
821 # we must *not* call any traceback construction again,
814 # because that would mess up use of %debug later on. So we
822 # because that would mess up use of %debug later on. So we
815 # simply report the failure and move on. The only
823 # simply report the failure and move on. The only
816 # limitation will be that this frame won't have locals
824 # limitation will be that this frame won't have locals
817 # listed in the call signature. Quite subtle problem...
825 # listed in the call signature. Quite subtle problem...
818 # I can't think of a good way to validate this in a unit
826 # I can't think of a good way to validate this in a unit
819 # test, but running a script consisting of:
827 # test, but running a script consisting of:
820 # dict( (k,v.strip()) for (k,v) in range(10) )
828 # dict( (k,v.strip()) for (k,v) in range(10) )
821 # will illustrate the error, if this exception catch is
829 # will illustrate the error, if this exception catch is
822 # disabled.
830 # disabled.
823 call = tpl_call_fail % func
831 call = tpl_call_fail % func
824
832
825 lvals = ''
833 lvals = ''
826 lvals_list = []
834 lvals_list = []
827 if self.include_vars:
835 if self.include_vars:
828 try:
836 try:
829 # we likely want to fix stackdata at some point, but
837 # we likely want to fix stackdata at some point, but
830 # still need a workaround.
838 # still need a workaround.
831 fibp = frame_info.variables_in_executing_piece
839 fibp = frame_info.variables_in_executing_piece
832 for var in fibp:
840 for var in fibp:
833 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
841 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
834 except Exception:
842 except Exception:
835 lvals_list.append(
843 lvals_list.append(
836 "Exception trying to inspect frame. No more locals available."
844 "Exception trying to inspect frame. No more locals available."
837 )
845 )
838 if lvals_list:
846 if lvals_list:
839 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
847 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
840
848
841 result = f'{link}{", " if call else ""}{call}\n'
849 result = f'{link}{", " if call else ""}{call}\n'
842 if frame_info._sd is None:
850 if frame_info._sd is None:
843 assert False
851 assert False
844 # fast fallback if file is too long
852 # fast fallback if file is too long
845 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
853 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
846 link = tpl_link % util_path.compress_user(frame_info.filename)
854 link = tpl_link % util_path.compress_user(frame_info.filename)
847 level = "%s %s\n" % (link, call)
855 level = "%s %s\n" % (link, call)
848 _line_format = PyColorize.Parser(
856 _line_format = PyColorize.Parser(
849 style=self.color_scheme_table.active_scheme_name, parent=self
857 style=self.color_scheme_table.active_scheme_name, parent=self
850 ).format2
858 ).format2
851 first_line = frame_info.code.co_firstlineno
859 first_line = frame_info.code.co_firstlineno
852 current_line = frame_info.lineno[0]
860 current_line = frame_info.lineno[0]
853 return "%s%s" % (
861 return "%s%s" % (
854 level,
862 level,
855 "".join(
863 "".join(
856 _simple_format_traceback_lines(
864 _simple_format_traceback_lines(
857 current_line,
865 current_line,
858 current_line - first_line,
866 current_line - first_line,
859 frame_info.raw_lines,
867 frame_info.raw_lines,
860 Colors,
868 Colors,
861 lvals,
869 lvals,
862 _line_format,
870 _line_format,
863 )
871 )
864 ),
872 ),
865 )
873 )
866 # result += "\n".join(frame_info.raw_lines)
874 # result += "\n".join(frame_info.raw_lines)
867 else:
875 else:
868 result += "".join(
876 result += "".join(
869 _format_traceback_lines(
877 _format_traceback_lines(
870 frame_info.lines, Colors, self.has_colors, lvals
878 frame_info.lines, Colors, self.has_colors, lvals
871 )
879 )
872 )
880 )
873 return result
881 return result
874
882
875 def prepare_header(self, etype, long_version=False):
883 def prepare_header(self, etype: str, long_version: bool = False):
876 colors = self.Colors # just a shorthand + quicker name lookup
884 colors = self.Colors # just a shorthand + quicker name lookup
877 colorsnormal = colors.Normal # used a lot
885 colorsnormal = colors.Normal # used a lot
878 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
886 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
879 width = min(75, get_terminal_size()[0])
887 width = min(75, get_terminal_size()[0])
880 if long_version:
888 if long_version:
881 # Header with the exception type, python version, and date
889 # Header with the exception type, python version, and date
882 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
890 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
883 date = time.ctime(time.time())
891 date = time.ctime(time.time())
884
892
885 head = '%s%s%s\n%s%s%s\n%s' % (colors.topline, '-' * width, colorsnormal,
893 head = "%s%s%s\n%s%s%s\n%s" % (
886 exc, ' ' * (width - len(str(etype)) - len(pyver)),
894 colors.topline,
887 pyver, date.rjust(width) )
895 "-" * width,
888 head += "\nA problem occurred executing Python code. Here is the sequence of function" \
896 colorsnormal,
889 "\ncalls leading up to the error, with the most recent (innermost) call last."
897 exc,
898 " " * (width - len(etype) - len(pyver)),
899 pyver,
900 date.rjust(width),
901 )
902 head += (
903 "\nA problem occurred executing Python code. Here is the sequence of function"
904 "\ncalls leading up to the error, with the most recent (innermost) call last."
905 )
890 else:
906 else:
891 # Simplified header
907 # Simplified header
892 head = '%s%s' % (exc, 'Traceback (most recent call last)'. \
908 head = "%s%s" % (
893 rjust(width - len(str(etype))) )
909 exc,
910 "Traceback (most recent call last)".rjust(width - len(etype)),
911 )
894
912
895 return head
913 return head
896
914
897 def format_exception(self, etype, evalue):
915 def format_exception(self, etype, evalue):
898 colors = self.Colors # just a shorthand + quicker name lookup
916 colors = self.Colors # just a shorthand + quicker name lookup
899 colorsnormal = colors.Normal # used a lot
917 colorsnormal = colors.Normal # used a lot
900 # Get (safely) a string form of the exception info
918 # Get (safely) a string form of the exception info
901 try:
919 try:
902 etype_str, evalue_str = map(str, (etype, evalue))
920 etype_str, evalue_str = map(str, (etype, evalue))
903 except:
921 except:
904 # User exception is improperly defined.
922 # User exception is improperly defined.
905 etype, evalue = str, sys.exc_info()[:2]
923 etype, evalue = str, sys.exc_info()[:2]
906 etype_str, evalue_str = map(str, (etype, evalue))
924 etype_str, evalue_str = map(str, (etype, evalue))
907 # ... and format it
925 # ... and format it
908 return ['%s%s%s: %s' % (colors.excName, etype_str,
926 return ['%s%s%s: %s' % (colors.excName, etype_str,
909 colorsnormal, py3compat.cast_unicode(evalue_str))]
927 colorsnormal, py3compat.cast_unicode(evalue_str))]
910
928
911 def format_exception_as_a_whole(
929 def format_exception_as_a_whole(
912 self,
930 self,
913 etype: type,
931 etype: type,
914 evalue: BaseException,
932 evalue: Optional[BaseException],
915 etb: Optional[TracebackType],
933 etb: Optional[TracebackType],
916 number_of_lines_of_context,
934 number_of_lines_of_context,
917 tb_offset: Optional[int],
935 tb_offset: Optional[int],
918 ):
936 ):
919 """Formats the header, traceback and exception message for a single exception.
937 """Formats the header, traceback and exception message for a single exception.
920
938
921 This may be called multiple times by Python 3 exception chaining
939 This may be called multiple times by Python 3 exception chaining
922 (PEP 3134).
940 (PEP 3134).
923 """
941 """
924 # some locals
942 # some locals
925 orig_etype = etype
943 orig_etype = etype
926 try:
944 try:
927 etype = etype.__name__
945 etype = etype.__name__
928 except AttributeError:
946 except AttributeError:
929 pass
947 pass
930
948
931 tb_offset = self.tb_offset if tb_offset is None else tb_offset
949 tb_offset = self.tb_offset if tb_offset is None else tb_offset
932 assert isinstance(tb_offset, int)
950 assert isinstance(tb_offset, int)
933 head = self.prepare_header(etype, self.long_header)
951 head = self.prepare_header(etype, self.long_header)
934 records = (
952 records = (
935 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
953 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
936 )
954 )
937
955
938 frames = []
956 frames = []
939 skipped = 0
957 skipped = 0
940 lastrecord = len(records) - 1
958 lastrecord = len(records) - 1
941 for i, record in enumerate(records):
959 for i, record in enumerate(records):
942 if (
960 if (
943 not isinstance(record._sd, stack_data.RepeatedFrames)
961 not isinstance(record._sd, stack_data.RepeatedFrames)
944 and self.skip_hidden
962 and self.skip_hidden
945 ):
963 ):
946 if (
964 if (
947 record.frame.f_locals.get("__tracebackhide__", 0)
965 record.frame.f_locals.get("__tracebackhide__", 0)
948 and i != lastrecord
966 and i != lastrecord
949 ):
967 ):
950 skipped += 1
968 skipped += 1
951 continue
969 continue
952 if skipped:
970 if skipped:
953 Colors = self.Colors # just a shorthand + quicker name lookup
971 Colors = self.Colors # just a shorthand + quicker name lookup
954 ColorsNormal = Colors.Normal # used a lot
972 ColorsNormal = Colors.Normal # used a lot
955 frames.append(
973 frames.append(
956 " %s[... skipping hidden %s frame]%s\n"
974 " %s[... skipping hidden %s frame]%s\n"
957 % (Colors.excName, skipped, ColorsNormal)
975 % (Colors.excName, skipped, ColorsNormal)
958 )
976 )
959 skipped = 0
977 skipped = 0
960 frames.append(self.format_record(record))
978 frames.append(self.format_record(record))
961 if skipped:
979 if skipped:
962 Colors = self.Colors # just a shorthand + quicker name lookup
980 Colors = self.Colors # just a shorthand + quicker name lookup
963 ColorsNormal = Colors.Normal # used a lot
981 ColorsNormal = Colors.Normal # used a lot
964 frames.append(
982 frames.append(
965 " %s[... skipping hidden %s frame]%s\n"
983 " %s[... skipping hidden %s frame]%s\n"
966 % (Colors.excName, skipped, ColorsNormal)
984 % (Colors.excName, skipped, ColorsNormal)
967 )
985 )
968
986
969 formatted_exception = self.format_exception(etype, evalue)
987 formatted_exception = self.format_exception(etype, evalue)
970 if records:
988 if records:
971 frame_info = records[-1]
989 frame_info = records[-1]
972 ipinst = get_ipython()
990 ipinst = get_ipython()
973 if ipinst is not None:
991 if ipinst is not None:
974 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
992 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
975
993
976 return [[head] + frames + [''.join(formatted_exception[0])]]
994 return [[head] + frames + [''.join(formatted_exception[0])]]
977
995
978 def get_records(
996 def get_records(
979 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
997 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
980 ):
998 ):
981 assert etb is not None
999 assert etb is not None
982 context = number_of_lines_of_context - 1
1000 context = number_of_lines_of_context - 1
983 after = context // 2
1001 after = context // 2
984 before = context - after
1002 before = context - after
985 if self.has_colors:
1003 if self.has_colors:
986 style = get_style_by_name("default")
1004 style = get_style_by_name("default")
987 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1005 style = stack_data.style_with_executing_node(style, self._tb_highlight)
988 formatter = Terminal256Formatter(style=style)
1006 formatter = Terminal256Formatter(style=style)
989 else:
1007 else:
990 formatter = None
1008 formatter = None
991 options = stack_data.Options(
1009 options = stack_data.Options(
992 before=before,
1010 before=before,
993 after=after,
1011 after=after,
994 pygments_formatter=formatter,
1012 pygments_formatter=formatter,
995 )
1013 )
996
1014
997 # Let's estimate the amount of code we will have to parse/highlight.
1015 # Let's estimate the amount of code we will have to parse/highlight.
998 cf = etb
1016 cf: Optional[TracebackType] = etb
999 max_len = 0
1017 max_len = 0
1000 tbs = []
1018 tbs = []
1001 while cf is not None:
1019 while cf is not None:
1002 source_file = inspect.getsourcefile(etb.tb_frame)
1020 source_file = inspect.getsourcefile(etb.tb_frame)
1003 lines, first = inspect.getsourcelines(etb.tb_frame)
1021 lines, first = inspect.getsourcelines(etb.tb_frame)
1004 max_len = max(max_len, first + len(lines))
1022 max_len = max(max_len, first + len(lines))
1005 tbs.append(cf)
1023 tbs.append(cf)
1006 cf = cf.tb_next
1024 cf = cf.tb_next
1007
1025
1008 if max_len > FAST_THRESHOLD:
1026 if max_len > FAST_THRESHOLD:
1009 FIs = []
1027 FIs = []
1010 for tb in tbs:
1028 for tb in tbs:
1011 frame = tb.tb_frame
1029 frame = tb.tb_frame
1012 lineno = (frame.f_lineno,)
1030 lineno = (frame.f_lineno,)
1013 code = frame.f_code
1031 code = frame.f_code
1014 filename = code.co_filename
1032 filename = code.co_filename
1015 FIs.append(FrameInfo("Raw frame", filename, lineno, frame, code))
1033 FIs.append(FrameInfo("Raw frame", filename, lineno, frame, code))
1016 return FIs
1034 return FIs
1017 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1035 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1018 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1036 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1019 return res
1037 return res
1020
1038
1021 def structured_traceback(
1039 def structured_traceback(
1022 self,
1040 self,
1023 etype: type,
1041 etype: type,
1024 evalue: Optional[BaseException],
1042 evalue: Optional[BaseException],
1025 etb: Optional[TracebackType],
1043 etb: Optional[TracebackType],
1026 tb_offset: Optional[int] = None,
1044 tb_offset: Optional[int] = None,
1027 number_of_lines_of_context: int = 5,
1045 number_of_lines_of_context: int = 5,
1028 ):
1046 ):
1029 """Return a nice text document describing the traceback."""
1047 """Return a nice text document describing the traceback."""
1030 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1048 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1031 tb_offset)
1049 tb_offset)
1032
1050
1033 colors = self.Colors # just a shorthand + quicker name lookup
1051 colors = self.Colors # just a shorthand + quicker name lookup
1034 colorsnormal = colors.Normal # used a lot
1052 colorsnormal = colors.Normal # used a lot
1035 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1053 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1036 structured_traceback_parts = [head]
1054 structured_traceback_parts = [head]
1037 chained_exceptions_tb_offset = 0
1055 chained_exceptions_tb_offset = 0
1038 lines_of_context = 3
1056 lines_of_context = 3
1039 formatted_exceptions = formatted_exception
1057 formatted_exceptions = formatted_exception
1040 exception = self.get_parts_of_chained_exception(evalue)
1058 exception = self.get_parts_of_chained_exception(evalue)
1041 if exception:
1059 if exception:
1042 assert evalue is not None
1060 assert evalue is not None
1043 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1061 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1044 etype, evalue, etb = exception
1062 etype, evalue, etb = exception
1045 else:
1063 else:
1046 evalue = None
1064 evalue = None
1047 chained_exc_ids = set()
1065 chained_exc_ids = set()
1048 while evalue:
1066 while evalue:
1049 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1067 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1050 chained_exceptions_tb_offset)
1068 chained_exceptions_tb_offset)
1051 exception = self.get_parts_of_chained_exception(evalue)
1069 exception = self.get_parts_of_chained_exception(evalue)
1052
1070
1053 if exception and not id(exception[1]) in chained_exc_ids:
1071 if exception and not id(exception[1]) in chained_exc_ids:
1054 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1072 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1055 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1073 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1056 etype, evalue, etb = exception
1074 etype, evalue, etb = exception
1057 else:
1075 else:
1058 evalue = None
1076 evalue = None
1059
1077
1060 # we want to see exceptions in a reversed order:
1078 # we want to see exceptions in a reversed order:
1061 # the first exception should be on top
1079 # the first exception should be on top
1062 for formatted_exception in reversed(formatted_exceptions):
1080 for formatted_exception in reversed(formatted_exceptions):
1063 structured_traceback_parts += formatted_exception
1081 structured_traceback_parts += formatted_exception
1064
1082
1065 return structured_traceback_parts
1083 return structured_traceback_parts
1066
1084
1067 def debugger(self, force: bool = False):
1085 def debugger(self, force: bool = False):
1068 """Call up the pdb debugger if desired, always clean up the tb
1086 """Call up the pdb debugger if desired, always clean up the tb
1069 reference.
1087 reference.
1070
1088
1071 Keywords:
1089 Keywords:
1072
1090
1073 - force(False): by default, this routine checks the instance call_pdb
1091 - force(False): by default, this routine checks the instance call_pdb
1074 flag and does not actually invoke the debugger if the flag is false.
1092 flag and does not actually invoke the debugger if the flag is false.
1075 The 'force' option forces the debugger to activate even if the flag
1093 The 'force' option forces the debugger to activate even if the flag
1076 is false.
1094 is false.
1077
1095
1078 If the call_pdb flag is set, the pdb interactive debugger is
1096 If the call_pdb flag is set, the pdb interactive debugger is
1079 invoked. In all cases, the self.tb reference to the current traceback
1097 invoked. In all cases, the self.tb reference to the current traceback
1080 is deleted to prevent lingering references which hamper memory
1098 is deleted to prevent lingering references which hamper memory
1081 management.
1099 management.
1082
1100
1083 Note that each call to pdb() does an 'import readline', so if your app
1101 Note that each call to pdb() does an 'import readline', so if your app
1084 requires a special setup for the readline completers, you'll have to
1102 requires a special setup for the readline completers, you'll have to
1085 fix that by hand after invoking the exception handler."""
1103 fix that by hand after invoking the exception handler."""
1086
1104
1087 if force or self.call_pdb:
1105 if force or self.call_pdb:
1088 if self.pdb is None:
1106 if self.pdb is None:
1089 self.pdb = self.debugger_cls()
1107 self.pdb = self.debugger_cls()
1090 # the system displayhook may have changed, restore the original
1108 # the system displayhook may have changed, restore the original
1091 # for pdb
1109 # for pdb
1092 display_trap = DisplayTrap(hook=sys.__displayhook__)
1110 display_trap = DisplayTrap(hook=sys.__displayhook__)
1093 with display_trap:
1111 with display_trap:
1094 self.pdb.reset()
1112 self.pdb.reset()
1095 # Find the right frame so we don't pop up inside ipython itself
1113 # Find the right frame so we don't pop up inside ipython itself
1096 if hasattr(self, 'tb') and self.tb is not None:
1114 if hasattr(self, 'tb') and self.tb is not None:
1097 etb = self.tb
1115 etb = self.tb
1098 else:
1116 else:
1099 etb = self.tb = sys.last_traceback
1117 etb = self.tb = sys.last_traceback
1100 while self.tb is not None and self.tb.tb_next is not None:
1118 while self.tb is not None and self.tb.tb_next is not None:
1101 assert self.tb.tb_next is not None
1119 assert self.tb.tb_next is not None
1102 self.tb = self.tb.tb_next
1120 self.tb = self.tb.tb_next
1103 if etb and etb.tb_next:
1121 if etb and etb.tb_next:
1104 etb = etb.tb_next
1122 etb = etb.tb_next
1105 self.pdb.botframe = etb.tb_frame
1123 self.pdb.botframe = etb.tb_frame
1106 self.pdb.interaction(None, etb)
1124 self.pdb.interaction(None, etb)
1107
1125
1108 if hasattr(self, 'tb'):
1126 if hasattr(self, 'tb'):
1109 del self.tb
1127 del self.tb
1110
1128
1111 def handler(self, info=None):
1129 def handler(self, info=None):
1112 (etype, evalue, etb) = info or sys.exc_info()
1130 (etype, evalue, etb) = info or sys.exc_info()
1113 self.tb = etb
1131 self.tb = etb
1114 ostream = self.ostream
1132 ostream = self.ostream
1115 ostream.flush()
1133 ostream.flush()
1116 ostream.write(self.text(etype, evalue, etb))
1134 ostream.write(self.text(etype, evalue, etb))
1117 ostream.write('\n')
1135 ostream.write('\n')
1118 ostream.flush()
1136 ostream.flush()
1119
1137
1120 # Changed so an instance can just be called as VerboseTB_inst() and print
1138 # Changed so an instance can just be called as VerboseTB_inst() and print
1121 # out the right info on its own.
1139 # out the right info on its own.
1122 def __call__(self, etype=None, evalue=None, etb=None):
1140 def __call__(self, etype=None, evalue=None, etb=None):
1123 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1141 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1124 if etb is None:
1142 if etb is None:
1125 self.handler()
1143 self.handler()
1126 else:
1144 else:
1127 self.handler((etype, evalue, etb))
1145 self.handler((etype, evalue, etb))
1128 try:
1146 try:
1129 self.debugger()
1147 self.debugger()
1130 except KeyboardInterrupt:
1148 except KeyboardInterrupt:
1131 print("\nKeyboardInterrupt")
1149 print("\nKeyboardInterrupt")
1132
1150
1133
1151
1134 #----------------------------------------------------------------------------
1152 #----------------------------------------------------------------------------
1135 class FormattedTB(VerboseTB, ListTB):
1153 class FormattedTB(VerboseTB, ListTB):
1136 """Subclass ListTB but allow calling with a traceback.
1154 """Subclass ListTB but allow calling with a traceback.
1137
1155
1138 It can thus be used as a sys.excepthook for Python > 2.1.
1156 It can thus be used as a sys.excepthook for Python > 2.1.
1139
1157
1140 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1158 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1141
1159
1142 Allows a tb_offset to be specified. This is useful for situations where
1160 Allows a tb_offset to be specified. This is useful for situations where
1143 one needs to remove a number of topmost frames from the traceback (such as
1161 one needs to remove a number of topmost frames from the traceback (such as
1144 occurs with python programs that themselves execute other python code,
1162 occurs with python programs that themselves execute other python code,
1145 like Python shells). """
1163 like Python shells). """
1146
1164
1147 mode: str
1165 mode: str
1148
1166
1149 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1167 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1150 ostream=None,
1168 ostream=None,
1151 tb_offset=0, long_header=False, include_vars=False,
1169 tb_offset=0, long_header=False, include_vars=False,
1152 check_cache=None, debugger_cls=None,
1170 check_cache=None, debugger_cls=None,
1153 parent=None, config=None):
1171 parent=None, config=None):
1154
1172
1155 # NEVER change the order of this list. Put new modes at the end:
1173 # NEVER change the order of this list. Put new modes at the end:
1156 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1174 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1157 self.verbose_modes = self.valid_modes[1:3]
1175 self.verbose_modes = self.valid_modes[1:3]
1158
1176
1159 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1177 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1160 ostream=ostream, tb_offset=tb_offset,
1178 ostream=ostream, tb_offset=tb_offset,
1161 long_header=long_header, include_vars=include_vars,
1179 long_header=long_header, include_vars=include_vars,
1162 check_cache=check_cache, debugger_cls=debugger_cls,
1180 check_cache=check_cache, debugger_cls=debugger_cls,
1163 parent=parent, config=config)
1181 parent=parent, config=config)
1164
1182
1165 # Different types of tracebacks are joined with different separators to
1183 # Different types of tracebacks are joined with different separators to
1166 # form a single string. They are taken from this dict
1184 # form a single string. They are taken from this dict
1167 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1185 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1168 Minimal='')
1186 Minimal='')
1169 # set_mode also sets the tb_join_char attribute
1187 # set_mode also sets the tb_join_char attribute
1170 self.set_mode(mode)
1188 self.set_mode(mode)
1171
1189
1172 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1190 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1173 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1191 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1174 mode = self.mode
1192 mode = self.mode
1175 if mode in self.verbose_modes:
1193 if mode in self.verbose_modes:
1176 # Verbose modes need a full traceback
1194 # Verbose modes need a full traceback
1177 return VerboseTB.structured_traceback(
1195 return VerboseTB.structured_traceback(
1178 self, etype, value, tb, tb_offset, number_of_lines_of_context
1196 self, etype, value, tb, tb_offset, number_of_lines_of_context
1179 )
1197 )
1180 elif mode == 'Minimal':
1198 elif mode == 'Minimal':
1181 return ListTB.get_exception_only(self, etype, value)
1199 return ListTB.get_exception_only(self, etype, value)
1182 else:
1200 else:
1183 # We must check the source cache because otherwise we can print
1201 # We must check the source cache because otherwise we can print
1184 # out-of-date source code.
1202 # out-of-date source code.
1185 self.check_cache()
1203 self.check_cache()
1186 # Now we can extract and format the exception
1204 # Now we can extract and format the exception
1187 return ListTB.structured_traceback(
1205 return ListTB.structured_traceback(
1188 self, etype, value, tb, tb_offset, number_of_lines_of_context
1206 self, etype, value, tb, tb_offset, number_of_lines_of_context
1189 )
1207 )
1190
1208
1191 def stb2text(self, stb):
1209 def stb2text(self, stb):
1192 """Convert a structured traceback (a list) to a string."""
1210 """Convert a structured traceback (a list) to a string."""
1193 return self.tb_join_char.join(stb)
1211 return self.tb_join_char.join(stb)
1194
1212
1195 def set_mode(self, mode: Optional[str] = None):
1213 def set_mode(self, mode: Optional[str] = None):
1196 """Switch to the desired mode.
1214 """Switch to the desired mode.
1197
1215
1198 If mode is not specified, cycles through the available modes."""
1216 If mode is not specified, cycles through the available modes."""
1199
1217
1200 if not mode:
1218 if not mode:
1201 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1219 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1202 len(self.valid_modes)
1220 len(self.valid_modes)
1203 self.mode = self.valid_modes[new_idx]
1221 self.mode = self.valid_modes[new_idx]
1204 elif mode not in self.valid_modes:
1222 elif mode not in self.valid_modes:
1205 raise ValueError(
1223 raise ValueError(
1206 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1224 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1207 "Valid modes: " + str(self.valid_modes)
1225 "Valid modes: " + str(self.valid_modes)
1208 )
1226 )
1209 else:
1227 else:
1210 assert isinstance(mode, str)
1228 assert isinstance(mode, str)
1211 self.mode = mode
1229 self.mode = mode
1212 # include variable details only in 'Verbose' mode
1230 # include variable details only in 'Verbose' mode
1213 self.include_vars = (self.mode == self.valid_modes[2])
1231 self.include_vars = (self.mode == self.valid_modes[2])
1214 # Set the join character for generating text tracebacks
1232 # Set the join character for generating text tracebacks
1215 self.tb_join_char = self._join_chars[self.mode]
1233 self.tb_join_char = self._join_chars[self.mode]
1216
1234
1217 # some convenient shortcuts
1235 # some convenient shortcuts
1218 def plain(self):
1236 def plain(self):
1219 self.set_mode(self.valid_modes[0])
1237 self.set_mode(self.valid_modes[0])
1220
1238
1221 def context(self):
1239 def context(self):
1222 self.set_mode(self.valid_modes[1])
1240 self.set_mode(self.valid_modes[1])
1223
1241
1224 def verbose(self):
1242 def verbose(self):
1225 self.set_mode(self.valid_modes[2])
1243 self.set_mode(self.valid_modes[2])
1226
1244
1227 def minimal(self):
1245 def minimal(self):
1228 self.set_mode(self.valid_modes[3])
1246 self.set_mode(self.valid_modes[3])
1229
1247
1230
1248
1231 #----------------------------------------------------------------------------
1249 #----------------------------------------------------------------------------
1232 class AutoFormattedTB(FormattedTB):
1250 class AutoFormattedTB(FormattedTB):
1233 """A traceback printer which can be called on the fly.
1251 """A traceback printer which can be called on the fly.
1234
1252
1235 It will find out about exceptions by itself.
1253 It will find out about exceptions by itself.
1236
1254
1237 A brief example::
1255 A brief example::
1238
1256
1239 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1257 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1240 try:
1258 try:
1241 ...
1259 ...
1242 except:
1260 except:
1243 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1261 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1244 """
1262 """
1245
1263
1246 def __call__(self, etype=None, evalue=None, etb=None,
1264 def __call__(self, etype=None, evalue=None, etb=None,
1247 out=None, tb_offset=None):
1265 out=None, tb_offset=None):
1248 """Print out a formatted exception traceback.
1266 """Print out a formatted exception traceback.
1249
1267
1250 Optional arguments:
1268 Optional arguments:
1251 - out: an open file-like object to direct output to.
1269 - out: an open file-like object to direct output to.
1252
1270
1253 - tb_offset: the number of frames to skip over in the stack, on a
1271 - tb_offset: the number of frames to skip over in the stack, on a
1254 per-call basis (this overrides temporarily the instance's tb_offset
1272 per-call basis (this overrides temporarily the instance's tb_offset
1255 given at initialization time."""
1273 given at initialization time."""
1256
1274
1257 if out is None:
1275 if out is None:
1258 out = self.ostream
1276 out = self.ostream
1259 out.flush()
1277 out.flush()
1260 out.write(self.text(etype, evalue, etb, tb_offset))
1278 out.write(self.text(etype, evalue, etb, tb_offset))
1261 out.write('\n')
1279 out.write('\n')
1262 out.flush()
1280 out.flush()
1263 # FIXME: we should remove the auto pdb behavior from here and leave
1281 # FIXME: we should remove the auto pdb behavior from here and leave
1264 # that to the clients.
1282 # that to the clients.
1265 try:
1283 try:
1266 self.debugger()
1284 self.debugger()
1267 except KeyboardInterrupt:
1285 except KeyboardInterrupt:
1268 print("\nKeyboardInterrupt")
1286 print("\nKeyboardInterrupt")
1269
1287
1270 def structured_traceback(
1288 def structured_traceback(
1271 self,
1289 self,
1272 etype=None,
1290 etype=None,
1273 value=None,
1291 value=None,
1274 tb=None,
1292 tb=None,
1275 tb_offset=None,
1293 tb_offset=None,
1276 number_of_lines_of_context=5,
1294 number_of_lines_of_context=5,
1277 ):
1295 ):
1278 etype: type
1296 etype: type
1279 value: BaseException
1297 value: BaseException
1280 # tb: TracebackType or tupleof tb types ?
1298 # tb: TracebackType or tupleof tb types ?
1281 if etype is None:
1299 if etype is None:
1282 etype, value, tb = sys.exc_info()
1300 etype, value, tb = sys.exc_info()
1283 if isinstance(tb, tuple):
1301 if isinstance(tb, tuple):
1284 # tb is a tuple if this is a chained exception.
1302 # tb is a tuple if this is a chained exception.
1285 self.tb = tb[0]
1303 self.tb = tb[0]
1286 else:
1304 else:
1287 self.tb = tb
1305 self.tb = tb
1288 return FormattedTB.structured_traceback(
1306 return FormattedTB.structured_traceback(
1289 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1307 self, etype, value, tb, tb_offset, number_of_lines_of_context)
1290
1308
1291
1309
1292 #---------------------------------------------------------------------------
1310 #---------------------------------------------------------------------------
1293
1311
1294 # A simple class to preserve Nathan's original functionality.
1312 # A simple class to preserve Nathan's original functionality.
1295 class ColorTB(FormattedTB):
1313 class ColorTB(FormattedTB):
1296 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1314 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1297
1315
1298 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1316 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1299 FormattedTB.__init__(self, color_scheme=color_scheme,
1317 FormattedTB.__init__(self, color_scheme=color_scheme,
1300 call_pdb=call_pdb, **kwargs)
1318 call_pdb=call_pdb, **kwargs)
1301
1319
1302
1320
1303 class SyntaxTB(ListTB):
1321 class SyntaxTB(ListTB):
1304 """Extension which holds some state: the last exception value"""
1322 """Extension which holds some state: the last exception value"""
1305
1323
1306 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1324 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1307 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1325 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1308 self.last_syntax_error = None
1326 self.last_syntax_error = None
1309
1327
1310 def __call__(self, etype, value, elist):
1328 def __call__(self, etype, value, elist):
1311 self.last_syntax_error = value
1329 self.last_syntax_error = value
1312
1330
1313 ListTB.__call__(self, etype, value, elist)
1331 ListTB.__call__(self, etype, value, elist)
1314
1332
1315 def structured_traceback(self, etype, value, elist, tb_offset=None,
1333 def structured_traceback(self, etype, value, elist, tb_offset=None,
1316 context=5):
1334 context=5):
1317 # If the source file has been edited, the line in the syntax error can
1335 # If the source file has been edited, the line in the syntax error can
1318 # be wrong (retrieved from an outdated cache). This replaces it with
1336 # be wrong (retrieved from an outdated cache). This replaces it with
1319 # the current value.
1337 # the current value.
1320 if isinstance(value, SyntaxError) \
1338 if isinstance(value, SyntaxError) \
1321 and isinstance(value.filename, str) \
1339 and isinstance(value.filename, str) \
1322 and isinstance(value.lineno, int):
1340 and isinstance(value.lineno, int):
1323 linecache.checkcache(value.filename)
1341 linecache.checkcache(value.filename)
1324 newtext = linecache.getline(value.filename, value.lineno)
1342 newtext = linecache.getline(value.filename, value.lineno)
1325 if newtext:
1343 if newtext:
1326 value.text = newtext
1344 value.text = newtext
1327 self.last_syntax_error = value
1345 self.last_syntax_error = value
1328 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1346 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1329 tb_offset=tb_offset, context=context)
1347 tb_offset=tb_offset, context=context)
1330
1348
1331 def clear_err_state(self):
1349 def clear_err_state(self):
1332 """Return the current error state and clear it"""
1350 """Return the current error state and clear it"""
1333 e = self.last_syntax_error
1351 e = self.last_syntax_error
1334 self.last_syntax_error = None
1352 self.last_syntax_error = None
1335 return e
1353 return e
1336
1354
1337 def stb2text(self, stb):
1355 def stb2text(self, stb):
1338 """Convert a structured traceback (a list) to a string."""
1356 """Convert a structured traceback (a list) to a string."""
1339 return ''.join(stb)
1357 return ''.join(stb)
1340
1358
1341
1359
1342 # some internal-use functions
1360 # some internal-use functions
1343 def text_repr(value):
1361 def text_repr(value):
1344 """Hopefully pretty robust repr equivalent."""
1362 """Hopefully pretty robust repr equivalent."""
1345 # this is pretty horrible but should always return *something*
1363 # this is pretty horrible but should always return *something*
1346 try:
1364 try:
1347 return pydoc.text.repr(value)
1365 return pydoc.text.repr(value)
1348 except KeyboardInterrupt:
1366 except KeyboardInterrupt:
1349 raise
1367 raise
1350 except:
1368 except:
1351 try:
1369 try:
1352 return repr(value)
1370 return repr(value)
1353 except KeyboardInterrupt:
1371 except KeyboardInterrupt:
1354 raise
1372 raise
1355 except:
1373 except:
1356 try:
1374 try:
1357 # all still in an except block so we catch
1375 # all still in an except block so we catch
1358 # getattr raising
1376 # getattr raising
1359 name = getattr(value, '__name__', None)
1377 name = getattr(value, '__name__', None)
1360 if name:
1378 if name:
1361 # ick, recursion
1379 # ick, recursion
1362 return text_repr(name)
1380 return text_repr(name)
1363 klass = getattr(value, '__class__', None)
1381 klass = getattr(value, '__class__', None)
1364 if klass:
1382 if klass:
1365 return '%s instance' % text_repr(klass)
1383 return '%s instance' % text_repr(klass)
1366 except KeyboardInterrupt:
1384 except KeyboardInterrupt:
1367 raise
1385 raise
1368 except:
1386 except:
1369 return 'UNRECOVERABLE REPR FAILURE'
1387 return 'UNRECOVERABLE REPR FAILURE'
1370
1388
1371
1389
1372 def eqrepr(value, repr=text_repr):
1390 def eqrepr(value, repr=text_repr):
1373 return '=%s' % repr(value)
1391 return '=%s' % repr(value)
1374
1392
1375
1393
1376 def nullrepr(value, repr=text_repr):
1394 def nullrepr(value, repr=text_repr):
1377 return ''
1395 return ''
@@ -1,675 +1,677 b''
1 """Various display related classes.
1 """Various display related classes.
2
2
3 Authors : MinRK, gregcaporaso, dannystaple
3 Authors : MinRK, gregcaporaso, dannystaple
4 """
4 """
5 from html import escape as html_escape
5 from html import escape as html_escape
6 from os.path import exists, isfile, splitext, abspath, join, isdir
6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 from os import walk, sep, fsdecode
7 from os import walk, sep, fsdecode
8
8
9 from IPython.core.display import DisplayObject, TextDisplayObject
9 from IPython.core.display import DisplayObject, TextDisplayObject
10
10
11 from typing import Tuple, Iterable
11 from typing import Tuple, Iterable, Optional
12
12
13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
14 'FileLink', 'FileLinks', 'Code']
14 'FileLink', 'FileLinks', 'Code']
15
15
16
16
17 class Audio(DisplayObject):
17 class Audio(DisplayObject):
18 """Create an audio object.
18 """Create an audio object.
19
19
20 When this object is returned by an input cell or passed to the
20 When this object is returned by an input cell or passed to the
21 display function, it will result in Audio controls being displayed
21 display function, it will result in Audio controls being displayed
22 in the frontend (only works in the notebook).
22 in the frontend (only works in the notebook).
23
23
24 Parameters
24 Parameters
25 ----------
25 ----------
26 data : numpy array, list, unicode, str or bytes
26 data : numpy array, list, unicode, str or bytes
27 Can be one of
27 Can be one of
28
28
29 * Numpy 1d array containing the desired waveform (mono)
29 * Numpy 1d array containing the desired waveform (mono)
30 * Numpy 2d array containing waveforms for each channel.
30 * Numpy 2d array containing waveforms for each channel.
31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
33 * List of float or integer representing the waveform (mono)
33 * List of float or integer representing the waveform (mono)
34 * String containing the filename
34 * String containing the filename
35 * Bytestring containing raw PCM data or
35 * Bytestring containing raw PCM data or
36 * URL pointing to a file on the web.
36 * URL pointing to a file on the web.
37
37
38 If the array option is used, the waveform will be normalized.
38 If the array option is used, the waveform will be normalized.
39
39
40 If a filename or url is used, the format support will be browser
40 If a filename or url is used, the format support will be browser
41 dependent.
41 dependent.
42 url : unicode
42 url : unicode
43 A URL to download the data from.
43 A URL to download the data from.
44 filename : unicode
44 filename : unicode
45 Path to a local file to load the data from.
45 Path to a local file to load the data from.
46 embed : boolean
46 embed : boolean
47 Should the audio data be embedded using a data URI (True) or should
47 Should the audio data be embedded using a data URI (True) or should
48 the original source be referenced. Set this to True if you want the
48 the original source be referenced. Set this to True if you want the
49 audio to playable later with no internet connection in the notebook.
49 audio to playable later with no internet connection in the notebook.
50
50
51 Default is `True`, unless the keyword argument `url` is set, then
51 Default is `True`, unless the keyword argument `url` is set, then
52 default value is `False`.
52 default value is `False`.
53 rate : integer
53 rate : integer
54 The sampling rate of the raw data.
54 The sampling rate of the raw data.
55 Only required when data parameter is being used as an array
55 Only required when data parameter is being used as an array
56 autoplay : bool
56 autoplay : bool
57 Set to True if the audio should immediately start playing.
57 Set to True if the audio should immediately start playing.
58 Default is `False`.
58 Default is `False`.
59 normalize : bool
59 normalize : bool
60 Whether audio should be normalized (rescaled) to the maximum possible
60 Whether audio should be normalized (rescaled) to the maximum possible
61 range. Default is `True`. When set to `False`, `data` must be between
61 range. Default is `True`. When set to `False`, `data` must be between
62 -1 and 1 (inclusive), otherwise an error is raised.
62 -1 and 1 (inclusive), otherwise an error is raised.
63 Applies only when `data` is a list or array of samples; other types of
63 Applies only when `data` is a list or array of samples; other types of
64 audio are never normalized.
64 audio are never normalized.
65
65
66 Examples
66 Examples
67 --------
67 --------
68
68
69 >>> import pytest
69 >>> import pytest
70 >>> np = pytest.importorskip("numpy")
70 >>> np = pytest.importorskip("numpy")
71
71
72 Generate a sound
72 Generate a sound
73
73
74 >>> import numpy as np
74 >>> import numpy as np
75 >>> framerate = 44100
75 >>> framerate = 44100
76 >>> t = np.linspace(0,5,framerate*5)
76 >>> t = np.linspace(0,5,framerate*5)
77 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
77 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
78 >>> Audio(data, rate=framerate)
78 >>> Audio(data, rate=framerate)
79 <IPython.lib.display.Audio object>
79 <IPython.lib.display.Audio object>
80
80
81 Can also do stereo or more channels
81 Can also do stereo or more channels
82
82
83 >>> dataleft = np.sin(2*np.pi*220*t)
83 >>> dataleft = np.sin(2*np.pi*220*t)
84 >>> dataright = np.sin(2*np.pi*224*t)
84 >>> dataright = np.sin(2*np.pi*224*t)
85 >>> Audio([dataleft, dataright], rate=framerate)
85 >>> Audio([dataleft, dataright], rate=framerate)
86 <IPython.lib.display.Audio object>
86 <IPython.lib.display.Audio object>
87
87
88 From URL:
88 From URL:
89
89
90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
92
92
93 From a File:
93 From a File:
94
94
95 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
95 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
96 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
96 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
97
97
98 From Bytes:
98 From Bytes:
99
99
100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
102
102
103 See Also
103 See Also
104 --------
104 --------
105 ipywidgets.Audio
105 ipywidgets.Audio
106
106
107 Audio widget with more more flexibility and options.
107 Audio widget with more more flexibility and options.
108
108
109 """
109 """
110 _read_flags = 'rb'
110 _read_flags = 'rb'
111
111
112 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
112 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
113 element_id=None):
113 element_id=None):
114 if filename is None and url is None and data is None:
114 if filename is None and url is None and data is None:
115 raise ValueError("No audio data found. Expecting filename, url, or data.")
115 raise ValueError("No audio data found. Expecting filename, url, or data.")
116 if embed is False and url is None:
116 if embed is False and url is None:
117 raise ValueError("No url found. Expecting url when embed=False")
117 raise ValueError("No url found. Expecting url when embed=False")
118
118
119 if url is not None and embed is not True:
119 if url is not None and embed is not True:
120 self.embed = False
120 self.embed = False
121 else:
121 else:
122 self.embed = True
122 self.embed = True
123 self.autoplay = autoplay
123 self.autoplay = autoplay
124 self.element_id = element_id
124 self.element_id = element_id
125 super(Audio, self).__init__(data=data, url=url, filename=filename)
125 super(Audio, self).__init__(data=data, url=url, filename=filename)
126
126
127 if self.data is not None and not isinstance(self.data, bytes):
127 if self.data is not None and not isinstance(self.data, bytes):
128 if rate is None:
128 if rate is None:
129 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
129 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
130 self.data = Audio._make_wav(data, rate, normalize)
130 self.data = Audio._make_wav(data, rate, normalize)
131
131
132 def reload(self):
132 def reload(self):
133 """Reload the raw data from file or URL."""
133 """Reload the raw data from file or URL."""
134 import mimetypes
134 import mimetypes
135 if self.embed:
135 if self.embed:
136 super(Audio, self).reload()
136 super(Audio, self).reload()
137
137
138 if self.filename is not None:
138 if self.filename is not None:
139 self.mimetype = mimetypes.guess_type(self.filename)[0]
139 self.mimetype = mimetypes.guess_type(self.filename)[0]
140 elif self.url is not None:
140 elif self.url is not None:
141 self.mimetype = mimetypes.guess_type(self.url)[0]
141 self.mimetype = mimetypes.guess_type(self.url)[0]
142 else:
142 else:
143 self.mimetype = "audio/wav"
143 self.mimetype = "audio/wav"
144
144
145 @staticmethod
145 @staticmethod
146 def _make_wav(data, rate, normalize):
146 def _make_wav(data, rate, normalize):
147 """ Transform a numpy array to a PCM bytestring """
147 """ Transform a numpy array to a PCM bytestring """
148 from io import BytesIO
148 from io import BytesIO
149 import wave
149 import wave
150
150
151 try:
151 try:
152 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
152 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
153 except ImportError:
153 except ImportError:
154 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
154 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
155
155
156 fp = BytesIO()
156 fp = BytesIO()
157 waveobj = wave.open(fp,mode='wb')
157 waveobj = wave.open(fp,mode='wb')
158 waveobj.setnchannels(nchan)
158 waveobj.setnchannels(nchan)
159 waveobj.setframerate(rate)
159 waveobj.setframerate(rate)
160 waveobj.setsampwidth(2)
160 waveobj.setsampwidth(2)
161 waveobj.setcomptype('NONE','NONE')
161 waveobj.setcomptype('NONE','NONE')
162 waveobj.writeframes(scaled)
162 waveobj.writeframes(scaled)
163 val = fp.getvalue()
163 val = fp.getvalue()
164 waveobj.close()
164 waveobj.close()
165
165
166 return val
166 return val
167
167
168 @staticmethod
168 @staticmethod
169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
170 import numpy as np
170 import numpy as np
171
171
172 data = np.array(data, dtype=float)
172 data = np.array(data, dtype=float)
173 if len(data.shape) == 1:
173 if len(data.shape) == 1:
174 nchan = 1
174 nchan = 1
175 elif len(data.shape) == 2:
175 elif len(data.shape) == 2:
176 # In wave files,channels are interleaved. E.g.,
176 # In wave files,channels are interleaved. E.g.,
177 # "L1R1L2R2..." for stereo. See
177 # "L1R1L2R2..." for stereo. See
178 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
178 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
179 # for channel ordering
179 # for channel ordering
180 nchan = data.shape[0]
180 nchan = data.shape[0]
181 data = data.T.ravel()
181 data = data.T.ravel()
182 else:
182 else:
183 raise ValueError('Array audio input must be a 1D or 2D array')
183 raise ValueError('Array audio input must be a 1D or 2D array')
184
184
185 max_abs_value = np.max(np.abs(data))
185 max_abs_value = np.max(np.abs(data))
186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
187 scaled = data / normalization_factor * 32767
187 scaled = data / normalization_factor * 32767
188 return scaled.astype("<h").tobytes(), nchan
188 return scaled.astype("<h").tobytes(), nchan
189
189
190 @staticmethod
190 @staticmethod
191 def _validate_and_normalize_without_numpy(data, normalize):
191 def _validate_and_normalize_without_numpy(data, normalize):
192 import array
192 import array
193 import sys
193 import sys
194
194
195 data = array.array('f', data)
195 data = array.array('f', data)
196
196
197 try:
197 try:
198 max_abs_value = float(max([abs(x) for x in data]))
198 max_abs_value = float(max([abs(x) for x in data]))
199 except TypeError as e:
199 except TypeError as e:
200 raise TypeError('Only lists of mono audio are '
200 raise TypeError('Only lists of mono audio are '
201 'supported if numpy is not installed') from e
201 'supported if numpy is not installed') from e
202
202
203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
204 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
204 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
205 if sys.byteorder == 'big':
205 if sys.byteorder == 'big':
206 scaled.byteswap()
206 scaled.byteswap()
207 nchan = 1
207 nchan = 1
208 return scaled.tobytes(), nchan
208 return scaled.tobytes(), nchan
209
209
210 @staticmethod
210 @staticmethod
211 def _get_normalization_factor(max_abs_value, normalize):
211 def _get_normalization_factor(max_abs_value, normalize):
212 if not normalize and max_abs_value > 1:
212 if not normalize and max_abs_value > 1:
213 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
213 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
214 return max_abs_value if normalize else 1
214 return max_abs_value if normalize else 1
215
215
216 def _data_and_metadata(self):
216 def _data_and_metadata(self):
217 """shortcut for returning metadata with url information, if defined"""
217 """shortcut for returning metadata with url information, if defined"""
218 md = {}
218 md = {}
219 if self.url:
219 if self.url:
220 md['url'] = self.url
220 md['url'] = self.url
221 if md:
221 if md:
222 return self.data, md
222 return self.data, md
223 else:
223 else:
224 return self.data
224 return self.data
225
225
226 def _repr_html_(self):
226 def _repr_html_(self):
227 src = """
227 src = """
228 <audio {element_id} controls="controls" {autoplay}>
228 <audio {element_id} controls="controls" {autoplay}>
229 <source src="{src}" type="{type}" />
229 <source src="{src}" type="{type}" />
230 Your browser does not support the audio element.
230 Your browser does not support the audio element.
231 </audio>
231 </audio>
232 """
232 """
233 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
233 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
234 element_id=self.element_id_attr())
234 element_id=self.element_id_attr())
235
235
236 def src_attr(self):
236 def src_attr(self):
237 import base64
237 import base64
238 if self.embed and (self.data is not None):
238 if self.embed and (self.data is not None):
239 data = base64=base64.b64encode(self.data).decode('ascii')
239 data = base64=base64.b64encode(self.data).decode('ascii')
240 return """data:{type};base64,{base64}""".format(type=self.mimetype,
240 return """data:{type};base64,{base64}""".format(type=self.mimetype,
241 base64=data)
241 base64=data)
242 elif self.url is not None:
242 elif self.url is not None:
243 return self.url
243 return self.url
244 else:
244 else:
245 return ""
245 return ""
246
246
247 def autoplay_attr(self):
247 def autoplay_attr(self):
248 if(self.autoplay):
248 if(self.autoplay):
249 return 'autoplay="autoplay"'
249 return 'autoplay="autoplay"'
250 else:
250 else:
251 return ''
251 return ''
252
252
253 def element_id_attr(self):
253 def element_id_attr(self):
254 if (self.element_id):
254 if (self.element_id):
255 return 'id="{element_id}"'.format(element_id=self.element_id)
255 return 'id="{element_id}"'.format(element_id=self.element_id)
256 else:
256 else:
257 return ''
257 return ''
258
258
259 class IFrame(object):
259 class IFrame(object):
260 """
260 """
261 Generic class to embed an iframe in an IPython notebook
261 Generic class to embed an iframe in an IPython notebook
262 """
262 """
263
263
264 iframe = """
264 iframe = """
265 <iframe
265 <iframe
266 width="{width}"
266 width="{width}"
267 height="{height}"
267 height="{height}"
268 src="{src}{params}"
268 src="{src}{params}"
269 frameborder="0"
269 frameborder="0"
270 allowfullscreen
270 allowfullscreen
271 {extras}
271 {extras}
272 ></iframe>
272 ></iframe>
273 """
273 """
274
274
275 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
275 def __init__(
276 self, src, width, height, extras: Optional[Iterable[str]] = None, **kwargs
277 ):
276 if extras is None:
278 if extras is None:
277 extras = []
279 extras = []
278
280
279 self.src = src
281 self.src = src
280 self.width = width
282 self.width = width
281 self.height = height
283 self.height = height
282 self.extras = extras
284 self.extras = extras
283 self.params = kwargs
285 self.params = kwargs
284
286
285 def _repr_html_(self):
287 def _repr_html_(self):
286 """return the embed iframe"""
288 """return the embed iframe"""
287 if self.params:
289 if self.params:
288 from urllib.parse import urlencode
290 from urllib.parse import urlencode
289 params = "?" + urlencode(self.params)
291 params = "?" + urlencode(self.params)
290 else:
292 else:
291 params = ""
293 params = ""
292 return self.iframe.format(
294 return self.iframe.format(
293 src=self.src,
295 src=self.src,
294 width=self.width,
296 width=self.width,
295 height=self.height,
297 height=self.height,
296 params=params,
298 params=params,
297 extras=" ".join(self.extras),
299 extras=" ".join(self.extras),
298 )
300 )
299
301
300
302
301 class YouTubeVideo(IFrame):
303 class YouTubeVideo(IFrame):
302 """Class for embedding a YouTube Video in an IPython session, based on its video id.
304 """Class for embedding a YouTube Video in an IPython session, based on its video id.
303
305
304 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
306 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
305 do::
307 do::
306
308
307 vid = YouTubeVideo("foo")
309 vid = YouTubeVideo("foo")
308 display(vid)
310 display(vid)
309
311
310 To start from 30 seconds::
312 To start from 30 seconds::
311
313
312 vid = YouTubeVideo("abc", start=30)
314 vid = YouTubeVideo("abc", start=30)
313 display(vid)
315 display(vid)
314
316
315 To calculate seconds from time as hours, minutes, seconds use
317 To calculate seconds from time as hours, minutes, seconds use
316 :class:`datetime.timedelta`::
318 :class:`datetime.timedelta`::
317
319
318 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
320 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
319
321
320 Other parameters can be provided as documented at
322 Other parameters can be provided as documented at
321 https://developers.google.com/youtube/player_parameters#Parameters
323 https://developers.google.com/youtube/player_parameters#Parameters
322
324
323 When converting the notebook using nbconvert, a jpeg representation of the video
325 When converting the notebook using nbconvert, a jpeg representation of the video
324 will be inserted in the document.
326 will be inserted in the document.
325 """
327 """
326
328
327 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
329 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
328 self.id=id
330 self.id=id
329 src = "https://www.youtube.com/embed/{0}".format(id)
331 src = "https://www.youtube.com/embed/{0}".format(id)
330 if allow_autoplay:
332 if allow_autoplay:
331 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
333 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
332 kwargs.update(autoplay=1, extras=extras)
334 kwargs.update(autoplay=1, extras=extras)
333 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
335 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
334
336
335 def _repr_jpeg_(self):
337 def _repr_jpeg_(self):
336 # Deferred import
338 # Deferred import
337 from urllib.request import urlopen
339 from urllib.request import urlopen
338
340
339 try:
341 try:
340 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
342 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
341 except IOError:
343 except IOError:
342 return None
344 return None
343
345
344 class VimeoVideo(IFrame):
346 class VimeoVideo(IFrame):
345 """
347 """
346 Class for embedding a Vimeo video in an IPython session, based on its video id.
348 Class for embedding a Vimeo video in an IPython session, based on its video id.
347 """
349 """
348
350
349 def __init__(self, id, width=400, height=300, **kwargs):
351 def __init__(self, id, width=400, height=300, **kwargs):
350 src="https://player.vimeo.com/video/{0}".format(id)
352 src="https://player.vimeo.com/video/{0}".format(id)
351 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
353 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
352
354
353 class ScribdDocument(IFrame):
355 class ScribdDocument(IFrame):
354 """
356 """
355 Class for embedding a Scribd document in an IPython session
357 Class for embedding a Scribd document in an IPython session
356
358
357 Use the start_page params to specify a starting point in the document
359 Use the start_page params to specify a starting point in the document
358 Use the view_mode params to specify display type one off scroll | slideshow | book
360 Use the view_mode params to specify display type one off scroll | slideshow | book
359
361
360 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
362 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
361
363
362 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
364 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
363 """
365 """
364
366
365 def __init__(self, id, width=400, height=300, **kwargs):
367 def __init__(self, id, width=400, height=300, **kwargs):
366 src="https://www.scribd.com/embeds/{0}/content".format(id)
368 src="https://www.scribd.com/embeds/{0}/content".format(id)
367 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
369 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
368
370
369 class FileLink(object):
371 class FileLink(object):
370 """Class for embedding a local file link in an IPython session, based on path
372 """Class for embedding a local file link in an IPython session, based on path
371
373
372 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
374 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
373
375
374 you would do::
376 you would do::
375
377
376 local_file = FileLink("my/data.txt")
378 local_file = FileLink("my/data.txt")
377 display(local_file)
379 display(local_file)
378
380
379 or in the HTML notebook, just::
381 or in the HTML notebook, just::
380
382
381 FileLink("my/data.txt")
383 FileLink("my/data.txt")
382 """
384 """
383
385
384 html_link_str = "<a href='%s' target='_blank'>%s</a>"
386 html_link_str = "<a href='%s' target='_blank'>%s</a>"
385
387
386 def __init__(self,
388 def __init__(self,
387 path,
389 path,
388 url_prefix='',
390 url_prefix='',
389 result_html_prefix='',
391 result_html_prefix='',
390 result_html_suffix='<br>'):
392 result_html_suffix='<br>'):
391 """
393 """
392 Parameters
394 Parameters
393 ----------
395 ----------
394 path : str
396 path : str
395 path to the file or directory that should be formatted
397 path to the file or directory that should be formatted
396 url_prefix : str
398 url_prefix : str
397 prefix to be prepended to all files to form a working link [default:
399 prefix to be prepended to all files to form a working link [default:
398 '']
400 '']
399 result_html_prefix : str
401 result_html_prefix : str
400 text to append to beginning to link [default: '']
402 text to append to beginning to link [default: '']
401 result_html_suffix : str
403 result_html_suffix : str
402 text to append at the end of link [default: '<br>']
404 text to append at the end of link [default: '<br>']
403 """
405 """
404 if isdir(path):
406 if isdir(path):
405 raise ValueError("Cannot display a directory using FileLink. "
407 raise ValueError("Cannot display a directory using FileLink. "
406 "Use FileLinks to display '%s'." % path)
408 "Use FileLinks to display '%s'." % path)
407 self.path = fsdecode(path)
409 self.path = fsdecode(path)
408 self.url_prefix = url_prefix
410 self.url_prefix = url_prefix
409 self.result_html_prefix = result_html_prefix
411 self.result_html_prefix = result_html_prefix
410 self.result_html_suffix = result_html_suffix
412 self.result_html_suffix = result_html_suffix
411
413
412 def _format_path(self):
414 def _format_path(self):
413 fp = ''.join([self.url_prefix, html_escape(self.path)])
415 fp = ''.join([self.url_prefix, html_escape(self.path)])
414 return ''.join([self.result_html_prefix,
416 return ''.join([self.result_html_prefix,
415 self.html_link_str % \
417 self.html_link_str % \
416 (fp, html_escape(self.path, quote=False)),
418 (fp, html_escape(self.path, quote=False)),
417 self.result_html_suffix])
419 self.result_html_suffix])
418
420
419 def _repr_html_(self):
421 def _repr_html_(self):
420 """return html link to file
422 """return html link to file
421 """
423 """
422 if not exists(self.path):
424 if not exists(self.path):
423 return ("Path (<tt>%s</tt>) doesn't exist. "
425 return ("Path (<tt>%s</tt>) doesn't exist. "
424 "It may still be in the process of "
426 "It may still be in the process of "
425 "being generated, or you may have the "
427 "being generated, or you may have the "
426 "incorrect path." % self.path)
428 "incorrect path." % self.path)
427
429
428 return self._format_path()
430 return self._format_path()
429
431
430 def __repr__(self):
432 def __repr__(self):
431 """return absolute path to file
433 """return absolute path to file
432 """
434 """
433 return abspath(self.path)
435 return abspath(self.path)
434
436
435 class FileLinks(FileLink):
437 class FileLinks(FileLink):
436 """Class for embedding local file links in an IPython session, based on path
438 """Class for embedding local file links in an IPython session, based on path
437
439
438 e.g. to embed links to files that were generated in the IPython notebook
440 e.g. to embed links to files that were generated in the IPython notebook
439 under ``my/data``, you would do::
441 under ``my/data``, you would do::
440
442
441 local_files = FileLinks("my/data")
443 local_files = FileLinks("my/data")
442 display(local_files)
444 display(local_files)
443
445
444 or in the HTML notebook, just::
446 or in the HTML notebook, just::
445
447
446 FileLinks("my/data")
448 FileLinks("my/data")
447 """
449 """
448 def __init__(self,
450 def __init__(self,
449 path,
451 path,
450 url_prefix='',
452 url_prefix='',
451 included_suffixes=None,
453 included_suffixes=None,
452 result_html_prefix='',
454 result_html_prefix='',
453 result_html_suffix='<br>',
455 result_html_suffix='<br>',
454 notebook_display_formatter=None,
456 notebook_display_formatter=None,
455 terminal_display_formatter=None,
457 terminal_display_formatter=None,
456 recursive=True):
458 recursive=True):
457 """
459 """
458 See :class:`FileLink` for the ``path``, ``url_prefix``,
460 See :class:`FileLink` for the ``path``, ``url_prefix``,
459 ``result_html_prefix`` and ``result_html_suffix`` parameters.
461 ``result_html_prefix`` and ``result_html_suffix`` parameters.
460
462
461 included_suffixes : list
463 included_suffixes : list
462 Filename suffixes to include when formatting output [default: include
464 Filename suffixes to include when formatting output [default: include
463 all files]
465 all files]
464
466
465 notebook_display_formatter : function
467 notebook_display_formatter : function
466 Used to format links for display in the notebook. See discussion of
468 Used to format links for display in the notebook. See discussion of
467 formatter functions below.
469 formatter functions below.
468
470
469 terminal_display_formatter : function
471 terminal_display_formatter : function
470 Used to format links for display in the terminal. See discussion of
472 Used to format links for display in the terminal. See discussion of
471 formatter functions below.
473 formatter functions below.
472
474
473 Formatter functions must be of the form::
475 Formatter functions must be of the form::
474
476
475 f(dirname, fnames, included_suffixes)
477 f(dirname, fnames, included_suffixes)
476
478
477 dirname : str
479 dirname : str
478 The name of a directory
480 The name of a directory
479 fnames : list
481 fnames : list
480 The files in that directory
482 The files in that directory
481 included_suffixes : list
483 included_suffixes : list
482 The file suffixes that should be included in the output (passing None
484 The file suffixes that should be included in the output (passing None
483 meansto include all suffixes in the output in the built-in formatters)
485 meansto include all suffixes in the output in the built-in formatters)
484 recursive : boolean
486 recursive : boolean
485 Whether to recurse into subdirectories. Default is True.
487 Whether to recurse into subdirectories. Default is True.
486
488
487 The function should return a list of lines that will be printed in the
489 The function should return a list of lines that will be printed in the
488 notebook (if passing notebook_display_formatter) or the terminal (if
490 notebook (if passing notebook_display_formatter) or the terminal (if
489 passing terminal_display_formatter). This function is iterated over for
491 passing terminal_display_formatter). This function is iterated over for
490 each directory in self.path. Default formatters are in place, can be
492 each directory in self.path. Default formatters are in place, can be
491 passed here to support alternative formatting.
493 passed here to support alternative formatting.
492
494
493 """
495 """
494 if isfile(path):
496 if isfile(path):
495 raise ValueError("Cannot display a file using FileLinks. "
497 raise ValueError("Cannot display a file using FileLinks. "
496 "Use FileLink to display '%s'." % path)
498 "Use FileLink to display '%s'." % path)
497 self.included_suffixes = included_suffixes
499 self.included_suffixes = included_suffixes
498 # remove trailing slashes for more consistent output formatting
500 # remove trailing slashes for more consistent output formatting
499 path = path.rstrip('/')
501 path = path.rstrip('/')
500
502
501 self.path = path
503 self.path = path
502 self.url_prefix = url_prefix
504 self.url_prefix = url_prefix
503 self.result_html_prefix = result_html_prefix
505 self.result_html_prefix = result_html_prefix
504 self.result_html_suffix = result_html_suffix
506 self.result_html_suffix = result_html_suffix
505
507
506 self.notebook_display_formatter = \
508 self.notebook_display_formatter = \
507 notebook_display_formatter or self._get_notebook_display_formatter()
509 notebook_display_formatter or self._get_notebook_display_formatter()
508 self.terminal_display_formatter = \
510 self.terminal_display_formatter = \
509 terminal_display_formatter or self._get_terminal_display_formatter()
511 terminal_display_formatter or self._get_terminal_display_formatter()
510
512
511 self.recursive = recursive
513 self.recursive = recursive
512
514
513 def _get_display_formatter(
515 def _get_display_formatter(
514 self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None
516 self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None
515 ):
517 ):
516 """generate built-in formatter function
518 """generate built-in formatter function
517
519
518 this is used to define both the notebook and terminal built-in
520 this is used to define both the notebook and terminal built-in
519 formatters as they only differ by some wrapper text for each entry
521 formatters as they only differ by some wrapper text for each entry
520
522
521 dirname_output_format: string to use for formatting directory
523 dirname_output_format: string to use for formatting directory
522 names, dirname will be substituted for a single "%s" which
524 names, dirname will be substituted for a single "%s" which
523 must appear in this string
525 must appear in this string
524 fname_output_format: string to use for formatting file names,
526 fname_output_format: string to use for formatting file names,
525 if a single "%s" appears in the string, fname will be substituted
527 if a single "%s" appears in the string, fname will be substituted
526 if two "%s" appear in the string, the path to fname will be
528 if two "%s" appear in the string, the path to fname will be
527 substituted for the first and fname will be substituted for the
529 substituted for the first and fname will be substituted for the
528 second
530 second
529 fp_format: string to use for formatting filepaths, must contain
531 fp_format: string to use for formatting filepaths, must contain
530 exactly two "%s" and the dirname will be substituted for the first
532 exactly two "%s" and the dirname will be substituted for the first
531 and fname will be substituted for the second
533 and fname will be substituted for the second
532 """
534 """
533 def f(dirname, fnames, included_suffixes=None):
535 def f(dirname, fnames, included_suffixes=None):
534 result = []
536 result = []
535 # begin by figuring out which filenames, if any,
537 # begin by figuring out which filenames, if any,
536 # are going to be displayed
538 # are going to be displayed
537 display_fnames = []
539 display_fnames = []
538 for fname in fnames:
540 for fname in fnames:
539 if (isfile(join(dirname,fname)) and
541 if (isfile(join(dirname,fname)) and
540 (included_suffixes is None or
542 (included_suffixes is None or
541 splitext(fname)[1] in included_suffixes)):
543 splitext(fname)[1] in included_suffixes)):
542 display_fnames.append(fname)
544 display_fnames.append(fname)
543
545
544 if len(display_fnames) == 0:
546 if len(display_fnames) == 0:
545 # if there are no filenames to display, don't print anything
547 # if there are no filenames to display, don't print anything
546 # (not even the directory name)
548 # (not even the directory name)
547 pass
549 pass
548 else:
550 else:
549 # otherwise print the formatted directory name followed by
551 # otherwise print the formatted directory name followed by
550 # the formatted filenames
552 # the formatted filenames
551 dirname_output_line = dirname_output_format % dirname
553 dirname_output_line = dirname_output_format % dirname
552 result.append(dirname_output_line)
554 result.append(dirname_output_line)
553 for fname in display_fnames:
555 for fname in display_fnames:
554 fp = fp_format % (dirname,fname)
556 fp = fp_format % (dirname,fname)
555 if fp_cleaner is not None:
557 if fp_cleaner is not None:
556 fp = fp_cleaner(fp)
558 fp = fp_cleaner(fp)
557 try:
559 try:
558 # output can include both a filepath and a filename...
560 # output can include both a filepath and a filename...
559 fname_output_line = fname_output_format % (fp, fname)
561 fname_output_line = fname_output_format % (fp, fname)
560 except TypeError:
562 except TypeError:
561 # ... or just a single filepath
563 # ... or just a single filepath
562 fname_output_line = fname_output_format % fname
564 fname_output_line = fname_output_format % fname
563 result.append(fname_output_line)
565 result.append(fname_output_line)
564 return result
566 return result
565 return f
567 return f
566
568
567 def _get_notebook_display_formatter(self,
569 def _get_notebook_display_formatter(self,
568 spacer="&nbsp;&nbsp;"):
570 spacer="&nbsp;&nbsp;"):
569 """ generate function to use for notebook formatting
571 """ generate function to use for notebook formatting
570 """
572 """
571 dirname_output_format = \
573 dirname_output_format = \
572 self.result_html_prefix + "%s/" + self.result_html_suffix
574 self.result_html_prefix + "%s/" + self.result_html_suffix
573 fname_output_format = \
575 fname_output_format = \
574 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
576 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
575 fp_format = self.url_prefix + '%s/%s'
577 fp_format = self.url_prefix + '%s/%s'
576 if sep == "\\":
578 if sep == "\\":
577 # Working on a platform where the path separator is "\", so
579 # Working on a platform where the path separator is "\", so
578 # must convert these to "/" for generating a URI
580 # must convert these to "/" for generating a URI
579 def fp_cleaner(fp):
581 def fp_cleaner(fp):
580 # Replace all occurrences of backslash ("\") with a forward
582 # Replace all occurrences of backslash ("\") with a forward
581 # slash ("/") - this is necessary on windows when a path is
583 # slash ("/") - this is necessary on windows when a path is
582 # provided as input, but we must link to a URI
584 # provided as input, but we must link to a URI
583 return fp.replace('\\','/')
585 return fp.replace('\\','/')
584 else:
586 else:
585 fp_cleaner = None
587 fp_cleaner = None
586
588
587 return self._get_display_formatter(dirname_output_format,
589 return self._get_display_formatter(dirname_output_format,
588 fname_output_format,
590 fname_output_format,
589 fp_format,
591 fp_format,
590 fp_cleaner)
592 fp_cleaner)
591
593
592 def _get_terminal_display_formatter(self,
594 def _get_terminal_display_formatter(self,
593 spacer=" "):
595 spacer=" "):
594 """ generate function to use for terminal formatting
596 """ generate function to use for terminal formatting
595 """
597 """
596 dirname_output_format = "%s/"
598 dirname_output_format = "%s/"
597 fname_output_format = spacer + "%s"
599 fname_output_format = spacer + "%s"
598 fp_format = '%s/%s'
600 fp_format = '%s/%s'
599
601
600 return self._get_display_formatter(dirname_output_format,
602 return self._get_display_formatter(dirname_output_format,
601 fname_output_format,
603 fname_output_format,
602 fp_format)
604 fp_format)
603
605
604 def _format_path(self):
606 def _format_path(self):
605 result_lines = []
607 result_lines = []
606 if self.recursive:
608 if self.recursive:
607 walked_dir = list(walk(self.path))
609 walked_dir = list(walk(self.path))
608 else:
610 else:
609 walked_dir = [next(walk(self.path))]
611 walked_dir = [next(walk(self.path))]
610 walked_dir.sort()
612 walked_dir.sort()
611 for dirname, subdirs, fnames in walked_dir:
613 for dirname, subdirs, fnames in walked_dir:
612 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
614 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
613 return '\n'.join(result_lines)
615 return '\n'.join(result_lines)
614
616
615 def __repr__(self):
617 def __repr__(self):
616 """return newline-separated absolute paths
618 """return newline-separated absolute paths
617 """
619 """
618 result_lines = []
620 result_lines = []
619 if self.recursive:
621 if self.recursive:
620 walked_dir = list(walk(self.path))
622 walked_dir = list(walk(self.path))
621 else:
623 else:
622 walked_dir = [next(walk(self.path))]
624 walked_dir = [next(walk(self.path))]
623 walked_dir.sort()
625 walked_dir.sort()
624 for dirname, subdirs, fnames in walked_dir:
626 for dirname, subdirs, fnames in walked_dir:
625 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
627 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
626 return '\n'.join(result_lines)
628 return '\n'.join(result_lines)
627
629
628
630
629 class Code(TextDisplayObject):
631 class Code(TextDisplayObject):
630 """Display syntax-highlighted source code.
632 """Display syntax-highlighted source code.
631
633
632 This uses Pygments to highlight the code for HTML and Latex output.
634 This uses Pygments to highlight the code for HTML and Latex output.
633
635
634 Parameters
636 Parameters
635 ----------
637 ----------
636 data : str
638 data : str
637 The code as a string
639 The code as a string
638 url : str
640 url : str
639 A URL to fetch the code from
641 A URL to fetch the code from
640 filename : str
642 filename : str
641 A local filename to load the code from
643 A local filename to load the code from
642 language : str
644 language : str
643 The short name of a Pygments lexer to use for highlighting.
645 The short name of a Pygments lexer to use for highlighting.
644 If not specified, it will guess the lexer based on the filename
646 If not specified, it will guess the lexer based on the filename
645 or the code. Available lexers: http://pygments.org/docs/lexers/
647 or the code. Available lexers: http://pygments.org/docs/lexers/
646 """
648 """
647 def __init__(self, data=None, url=None, filename=None, language=None):
649 def __init__(self, data=None, url=None, filename=None, language=None):
648 self.language = language
650 self.language = language
649 super().__init__(data=data, url=url, filename=filename)
651 super().__init__(data=data, url=url, filename=filename)
650
652
651 def _get_lexer(self):
653 def _get_lexer(self):
652 if self.language:
654 if self.language:
653 from pygments.lexers import get_lexer_by_name
655 from pygments.lexers import get_lexer_by_name
654 return get_lexer_by_name(self.language)
656 return get_lexer_by_name(self.language)
655 elif self.filename:
657 elif self.filename:
656 from pygments.lexers import get_lexer_for_filename
658 from pygments.lexers import get_lexer_for_filename
657 return get_lexer_for_filename(self.filename)
659 return get_lexer_for_filename(self.filename)
658 else:
660 else:
659 from pygments.lexers import guess_lexer
661 from pygments.lexers import guess_lexer
660 return guess_lexer(self.data)
662 return guess_lexer(self.data)
661
663
662 def __repr__(self):
664 def __repr__(self):
663 return self.data
665 return self.data
664
666
665 def _repr_html_(self):
667 def _repr_html_(self):
666 from pygments import highlight
668 from pygments import highlight
667 from pygments.formatters import HtmlFormatter
669 from pygments.formatters import HtmlFormatter
668 fmt = HtmlFormatter()
670 fmt = HtmlFormatter()
669 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
671 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
670 return style + highlight(self.data, self._get_lexer(), fmt)
672 return style + highlight(self.data, self._get_lexer(), fmt)
671
673
672 def _repr_latex_(self):
674 def _repr_latex_(self):
673 from pygments import highlight
675 from pygments import highlight
674 from pygments.formatters import LatexFormatter
676 from pygments.formatters import LatexFormatter
675 return highlight(self.data, self._get_lexer(), LatexFormatter())
677 return highlight(self.data, self._get_lexer(), LatexFormatter())
@@ -1,46 +1,48 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Timezone utilities
3 Timezone utilities
4
4
5 Just UTC-awareness right now
5 Just UTC-awareness right now
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from datetime import tzinfo, timedelta, datetime
19 from datetime import tzinfo, timedelta, datetime
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Code
22 # Code
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # constant for zero offset
24 # constant for zero offset
25 ZERO = timedelta(0)
25 ZERO = timedelta(0)
26
26
27 class tzUTC(tzinfo):
27 class tzUTC(tzinfo):
28 """tzinfo object for UTC (zero offset)"""
28 """tzinfo object for UTC (zero offset)"""
29
29
30 def utcoffset(self, d):
30 def utcoffset(self, d):
31 return ZERO
31 return ZERO
32
32
33 def dst(self, d):
33 def dst(self, d):
34 return ZERO
34 return ZERO
35
35
36 UTC = tzUTC()
36
37 UTC = tzUTC() # type: ignore[abstract]
38
37
39
38 def utc_aware(unaware):
40 def utc_aware(unaware):
39 """decorator for adding UTC tzinfo to datetime's utcfoo methods"""
41 """decorator for adding UTC tzinfo to datetime's utcfoo methods"""
40 def utc_method(*args, **kwargs):
42 def utc_method(*args, **kwargs):
41 dt = unaware(*args, **kwargs)
43 dt = unaware(*args, **kwargs)
42 return dt.replace(tzinfo=UTC)
44 return dt.replace(tzinfo=UTC)
43 return utc_method
45 return utc_method
44
46
45 utcfromtimestamp = utc_aware(datetime.utcfromtimestamp)
47 utcfromtimestamp = utc_aware(datetime.utcfromtimestamp)
46 utcnow = utc_aware(datetime.utcnow)
48 utcnow = utc_aware(datetime.utcnow)
@@ -1,3 +1,32 b''
1 [build-system]
1 [build-system]
2 requires = ["setuptools >= 51.0.0"]
2 requires = ["setuptools >= 51.0.0"]
3 build-backend = "setuptools.build_meta"
3 build-backend = "setuptools.build_meta"
4 [tool.mypy]
5 python_version = 3.8
6 ignore_missing_imports = true
7 follow_imports = 'silent'
8 exclude = [
9 'test_\.+\.py',
10 'IPython.utils.tests.test_wildcard',
11 'testing',
12 'tests',
13 'PyColorize.py',
14 '_process_win32_controller.py',
15 'IPython/core/application.py',
16 'IPython/core/completerlib.py',
17 'IPython/core/displaypub.py',
18 'IPython/core/historyapp.py',
19 #'IPython/core/interactiveshell.py',
20 'IPython/core/magic.py',
21 'IPython/core/profileapp.py',
22 'IPython/core/ultratb.py',
23 'IPython/lib/deepreload.py',
24 'IPython/lib/pretty.py',
25 'IPython/sphinxext/ipython_directive.py',
26 'IPython/terminal/ipapp.py',
27 'IPython/utils/_process_win32.py',
28 'IPython/utils/path.py',
29 'IPython/utils/timing.py',
30 'IPython/utils/text.py'
31 ]
32
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now