##// END OF EJS Templates
Add tests for resolving backend name and gui loop
Ian Thomas -
Show More
@@ -1,391 +1,391 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats."""
2 """Top-level display functions for displaying object in different formats."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from binascii import b2a_hex
8 from binascii import b2a_hex
9 import os
9 import os
10 import sys
10 import sys
11 import warnings
11 import warnings
12
12
13 __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
13 __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # utility functions
16 # utility functions
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 def _merge(d1, d2):
20 def _merge(d1, d2):
21 """Like update, but merges sub-dicts instead of clobbering at the top level.
21 """Like update, but merges sub-dicts instead of clobbering at the top level.
22
22
23 Updates d1 in-place
23 Updates d1 in-place
24 """
24 """
25
25
26 if not isinstance(d2, dict) or not isinstance(d1, dict):
26 if not isinstance(d2, dict) or not isinstance(d1, dict):
27 return d2
27 return d2
28 for key, value in d2.items():
28 for key, value in d2.items():
29 d1[key] = _merge(d1.get(key), value)
29 d1[key] = _merge(d1.get(key), value)
30 return d1
30 return d1
31
31
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Main functions
34 # Main functions
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class _Sentinel:
37 class _Sentinel:
38 def __repr__(self):
38 def __repr__(self):
39 return "<deprecated>"
39 return "<deprecated>"
40
40
41
41
42 _sentinel = _Sentinel()
42 _sentinel = _Sentinel()
43
43
44 # use * to indicate transient is keyword-only
44 # use * to indicate transient is keyword-only
45 def publish_display_data(
45 def publish_display_data(
46 data, metadata=None, source=_sentinel, *, transient=None, **kwargs
46 data, metadata=None, source=_sentinel, *, transient=None, **kwargs
47 ):
47 ):
48 """Publish data and metadata to all frontends.
48 """Publish data and metadata to all frontends.
49
49
50 See the ``display_data`` message in the messaging documentation for
50 See the ``display_data`` message in the messaging documentation for
51 more details about this message type.
51 more details about this message type.
52
52
53 Keys of data and metadata can be any mime-type.
53 Keys of data and metadata can be any mime-type.
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 data : dict
57 data : dict
58 A dictionary having keys that are valid MIME types (like
58 A dictionary having keys that are valid MIME types (like
59 'text/plain' or 'image/svg+xml') and values that are the data for
59 'text/plain' or 'image/svg+xml') and values that are the data for
60 that MIME type. The data itself must be a JSON'able data
60 that MIME type. The data itself must be a JSON'able data
61 structure. Minimally all data should have the 'text/plain' data,
61 structure. Minimally all data should have the 'text/plain' data,
62 which can be displayed by all frontends. If more than the plain
62 which can be displayed by all frontends. If more than the plain
63 text is given, it is up to the frontend to decide which
63 text is given, it is up to the frontend to decide which
64 representation to use.
64 representation to use.
65 metadata : dict
65 metadata : dict
66 A dictionary for metadata related to the data. This can contain
66 A dictionary for metadata related to the data. This can contain
67 arbitrary key, value pairs that frontends can use to interpret
67 arbitrary key, value pairs that frontends can use to interpret
68 the data. mime-type keys matching those in data can be used
68 the data. mime-type keys matching those in data can be used
69 to specify metadata about particular representations.
69 to specify metadata about particular representations.
70 source : str, deprecated
70 source : str, deprecated
71 Unused.
71 Unused.
72 transient : dict, keyword-only
72 transient : dict, keyword-only
73 A dictionary of transient data, such as display_id.
73 A dictionary of transient data, such as display_id.
74 """
74 """
75 from IPython.core.interactiveshell import InteractiveShell
75 from IPython.core.interactiveshell import InteractiveShell
76
76
77 if source is not _sentinel:
77 if source is not _sentinel:
78 warnings.warn(
78 warnings.warn(
79 "The `source` parameter emit a deprecation warning since"
79 "The `source` parameter emit a deprecation warning since"
80 " IPython 8.0, it had no effects for a long time and will "
80 " IPython 8.0, it had no effects for a long time and will "
81 " be removed in future versions.",
81 " be removed in future versions.",
82 DeprecationWarning,
82 DeprecationWarning,
83 stacklevel=2,
83 stacklevel=2,
84 )
84 )
85 display_pub = InteractiveShell.instance().display_pub
85 display_pub = InteractiveShell.instance().display_pub
86
86
87 # only pass transient if supplied,
87 # only pass transient if supplied,
88 # to avoid errors with older ipykernel.
88 # to avoid errors with older ipykernel.
89 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
89 # TODO: We could check for ipykernel version and provide a detailed upgrade message.
90 if transient:
90 if transient:
91 kwargs['transient'] = transient
91 kwargs['transient'] = transient
92
92
93 display_pub.publish(
93 display_pub.publish(
94 data=data,
94 data=data,
95 metadata=metadata,
95 metadata=metadata,
96 **kwargs
96 **kwargs
97 )
97 )
98
98
99
99
100 def _new_id():
100 def _new_id():
101 """Generate a new random text id with urandom"""
101 """Generate a new random text id with urandom"""
102 return b2a_hex(os.urandom(16)).decode('ascii')
102 return b2a_hex(os.urandom(16)).decode('ascii')
103
103
104
104
105 def display(
105 def display(
106 *objs,
106 *objs,
107 include=None,
107 include=None,
108 exclude=None,
108 exclude=None,
109 metadata=None,
109 metadata=None,
110 transient=None,
110 transient=None,
111 display_id=None,
111 display_id=None,
112 raw=False,
112 raw=False,
113 clear=False,
113 clear=False,
114 **kwargs
114 **kwargs,
115 ):
115 ):
116 """Display a Python object in all frontends.
116 """Display a Python object in all frontends.
117
117
118 By default all representations will be computed and sent to the frontends.
118 By default all representations will be computed and sent to the frontends.
119 Frontends can decide which representation is used and how.
119 Frontends can decide which representation is used and how.
120
120
121 In terminal IPython this will be similar to using :func:`print`, for use in richer
121 In terminal IPython this will be similar to using :func:`print`, for use in richer
122 frontends see Jupyter notebook examples with rich display logic.
122 frontends see Jupyter notebook examples with rich display logic.
123
123
124 Parameters
124 Parameters
125 ----------
125 ----------
126 *objs : object
126 *objs : object
127 The Python objects to display.
127 The Python objects to display.
128 raw : bool, optional
128 raw : bool, optional
129 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
129 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
130 or Python objects that need to be formatted before display? [default: False]
130 or Python objects that need to be formatted before display? [default: False]
131 include : list, tuple or set, optional
131 include : list, tuple or set, optional
132 A list of format type strings (MIME types) to include in the
132 A list of format type strings (MIME types) to include in the
133 format data dict. If this is set *only* the format types included
133 format data dict. If this is set *only* the format types included
134 in this list will be computed.
134 in this list will be computed.
135 exclude : list, tuple or set, optional
135 exclude : list, tuple or set, optional
136 A list of format type strings (MIME types) to exclude in the format
136 A list of format type strings (MIME types) to exclude in the format
137 data dict. If this is set all format types will be computed,
137 data dict. If this is set all format types will be computed,
138 except for those included in this argument.
138 except for those included in this argument.
139 metadata : dict, optional
139 metadata : dict, optional
140 A dictionary of metadata to associate with the output.
140 A dictionary of metadata to associate with the output.
141 mime-type keys in this dictionary will be associated with the individual
141 mime-type keys in this dictionary will be associated with the individual
142 representation formats, if they exist.
142 representation formats, if they exist.
143 transient : dict, optional
143 transient : dict, optional
144 A dictionary of transient data to associate with the output.
144 A dictionary of transient data to associate with the output.
145 Data in this dict should not be persisted to files (e.g. notebooks).
145 Data in this dict should not be persisted to files (e.g. notebooks).
146 display_id : str, bool optional
146 display_id : str, bool optional
147 Set an id for the display.
147 Set an id for the display.
148 This id can be used for updating this display area later via update_display.
148 This id can be used for updating this display area later via update_display.
149 If given as `True`, generate a new `display_id`
149 If given as `True`, generate a new `display_id`
150 clear : bool, optional
150 clear : bool, optional
151 Should the output area be cleared before displaying anything? If True,
151 Should the output area be cleared before displaying anything? If True,
152 this will wait for additional output before clearing. [default: False]
152 this will wait for additional output before clearing. [default: False]
153 **kwargs : additional keyword-args, optional
153 **kwargs : additional keyword-args, optional
154 Additional keyword-arguments are passed through to the display publisher.
154 Additional keyword-arguments are passed through to the display publisher.
155
155
156 Returns
156 Returns
157 -------
157 -------
158 handle: DisplayHandle
158 handle: DisplayHandle
159 Returns a handle on updatable displays for use with :func:`update_display`,
159 Returns a handle on updatable displays for use with :func:`update_display`,
160 if `display_id` is given. Returns :any:`None` if no `display_id` is given
160 if `display_id` is given. Returns :any:`None` if no `display_id` is given
161 (default).
161 (default).
162
162
163 Examples
163 Examples
164 --------
164 --------
165 >>> class Json(object):
165 >>> class Json(object):
166 ... def __init__(self, json):
166 ... def __init__(self, json):
167 ... self.json = json
167 ... self.json = json
168 ... def _repr_pretty_(self, pp, cycle):
168 ... def _repr_pretty_(self, pp, cycle):
169 ... import json
169 ... import json
170 ... pp.text(json.dumps(self.json, indent=2))
170 ... pp.text(json.dumps(self.json, indent=2))
171 ... def __repr__(self):
171 ... def __repr__(self):
172 ... return str(self.json)
172 ... return str(self.json)
173 ...
173 ...
174
174
175 >>> d = Json({1:2, 3: {4:5}})
175 >>> d = Json({1:2, 3: {4:5}})
176
176
177 >>> print(d)
177 >>> print(d)
178 {1: 2, 3: {4: 5}}
178 {1: 2, 3: {4: 5}}
179
179
180 >>> display(d)
180 >>> display(d)
181 {
181 {
182 "1": 2,
182 "1": 2,
183 "3": {
183 "3": {
184 "4": 5
184 "4": 5
185 }
185 }
186 }
186 }
187
187
188 >>> def int_formatter(integer, pp, cycle):
188 >>> def int_formatter(integer, pp, cycle):
189 ... pp.text('I'*integer)
189 ... pp.text('I'*integer)
190
190
191 >>> plain = get_ipython().display_formatter.formatters['text/plain']
191 >>> plain = get_ipython().display_formatter.formatters['text/plain']
192 >>> plain.for_type(int, int_formatter)
192 >>> plain.for_type(int, int_formatter)
193 <function _repr_pprint at 0x...>
193 <function _repr_pprint at 0x...>
194 >>> display(7-5)
194 >>> display(7-5)
195 II
195 II
196
196
197 >>> del plain.type_printers[int]
197 >>> del plain.type_printers[int]
198 >>> display(7-5)
198 >>> display(7-5)
199 2
199 2
200
200
201 See Also
201 See Also
202 --------
202 --------
203 :func:`update_display`
203 :func:`update_display`
204
204
205 Notes
205 Notes
206 -----
206 -----
207 In Python, objects can declare their textual representation using the
207 In Python, objects can declare their textual representation using the
208 `__repr__` method. IPython expands on this idea and allows objects to declare
208 `__repr__` method. IPython expands on this idea and allows objects to declare
209 other, rich representations including:
209 other, rich representations including:
210
210
211 - HTML
211 - HTML
212 - JSON
212 - JSON
213 - PNG
213 - PNG
214 - JPEG
214 - JPEG
215 - SVG
215 - SVG
216 - LaTeX
216 - LaTeX
217
217
218 A single object can declare some or all of these representations; all are
218 A single object can declare some or all of these representations; all are
219 handled by IPython's display system.
219 handled by IPython's display system.
220
220
221 The main idea of the first approach is that you have to implement special
221 The main idea of the first approach is that you have to implement special
222 display methods when you define your class, one for each representation you
222 display methods when you define your class, one for each representation you
223 want to use. Here is a list of the names of the special methods and the
223 want to use. Here is a list of the names of the special methods and the
224 values they must return:
224 values they must return:
225
225
226 - `_repr_html_`: return raw HTML as a string, or a tuple (see below).
226 - `_repr_html_`: return raw HTML as a string, or a tuple (see below).
227 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
227 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
228 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
228 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
229 - `_repr_png_`: return raw PNG data, or a tuple (see below).
229 - `_repr_png_`: return raw PNG data, or a tuple (see below).
230 - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
230 - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
231 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
231 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
232 or a tuple (see below).
232 or a tuple (see below).
233 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
233 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
234 from all mimetypes to data.
234 from all mimetypes to data.
235 Use this for any mime-type not listed above.
235 Use this for any mime-type not listed above.
236
236
237 The above functions may also return the object's metadata alonside the
237 The above functions may also return the object's metadata alonside the
238 data. If the metadata is available, the functions will return a tuple
238 data. If the metadata is available, the functions will return a tuple
239 containing the data and metadata, in that order. If there is no metadata
239 containing the data and metadata, in that order. If there is no metadata
240 available, then the functions will return the data only.
240 available, then the functions will return the data only.
241
241
242 When you are directly writing your own classes, you can adapt them for
242 When you are directly writing your own classes, you can adapt them for
243 display in IPython by following the above approach. But in practice, you
243 display in IPython by following the above approach. But in practice, you
244 often need to work with existing classes that you can't easily modify.
244 often need to work with existing classes that you can't easily modify.
245
245
246 You can refer to the documentation on integrating with the display system in
246 You can refer to the documentation on integrating with the display system in
247 order to register custom formatters for already existing types
247 order to register custom formatters for already existing types
248 (:ref:`integrating_rich_display`).
248 (:ref:`integrating_rich_display`).
249
249
250 .. versionadded:: 5.4 display available without import
250 .. versionadded:: 5.4 display available without import
251 .. versionadded:: 6.1 display available without import
251 .. versionadded:: 6.1 display available without import
252
252
253 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
253 Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
254 the user without import. If you are using display in a document that might
254 the user without import. If you are using display in a document that might
255 be used in a pure python context or with older version of IPython, use the
255 be used in a pure python context or with older version of IPython, use the
256 following import at the top of your file::
256 following import at the top of your file::
257
257
258 from IPython.display import display
258 from IPython.display import display
259
259
260 """
260 """
261 from IPython.core.interactiveshell import InteractiveShell
261 from IPython.core.interactiveshell import InteractiveShell
262
262
263 if not InteractiveShell.initialized():
263 if not InteractiveShell.initialized():
264 # Directly print objects.
264 # Directly print objects.
265 print(*objs)
265 print(*objs)
266 return
266 return
267
267
268 if transient is None:
268 if transient is None:
269 transient = {}
269 transient = {}
270 if metadata is None:
270 if metadata is None:
271 metadata={}
271 metadata={}
272 if display_id:
272 if display_id:
273 if display_id is True:
273 if display_id is True:
274 display_id = _new_id()
274 display_id = _new_id()
275 transient['display_id'] = display_id
275 transient['display_id'] = display_id
276 if kwargs.get('update') and 'display_id' not in transient:
276 if kwargs.get('update') and 'display_id' not in transient:
277 raise TypeError('display_id required for update_display')
277 raise TypeError('display_id required for update_display')
278 if transient:
278 if transient:
279 kwargs['transient'] = transient
279 kwargs['transient'] = transient
280
280
281 if not objs and display_id:
281 if not objs and display_id:
282 # if given no objects, but still a request for a display_id,
282 # if given no objects, but still a request for a display_id,
283 # we assume the user wants to insert an empty output that
283 # we assume the user wants to insert an empty output that
284 # can be updated later
284 # can be updated later
285 objs = [{}]
285 objs = [{}]
286 raw = True
286 raw = True
287
287
288 if not raw:
288 if not raw:
289 format = InteractiveShell.instance().display_formatter.format
289 format = InteractiveShell.instance().display_formatter.format
290
290
291 if clear:
291 if clear:
292 clear_output(wait=True)
292 clear_output(wait=True)
293
293
294 for obj in objs:
294 for obj in objs:
295 if raw:
295 if raw:
296 publish_display_data(data=obj, metadata=metadata, **kwargs)
296 publish_display_data(data=obj, metadata=metadata, **kwargs)
297 else:
297 else:
298 format_dict, md_dict = format(obj, include=include, exclude=exclude)
298 format_dict, md_dict = format(obj, include=include, exclude=exclude)
299 if not format_dict:
299 if not format_dict:
300 # nothing to display (e.g. _ipython_display_ took over)
300 # nothing to display (e.g. _ipython_display_ took over)
301 continue
301 continue
302 if metadata:
302 if metadata:
303 # kwarg-specified metadata gets precedence
303 # kwarg-specified metadata gets precedence
304 _merge(md_dict, metadata)
304 _merge(md_dict, metadata)
305 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
305 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
306 if display_id:
306 if display_id:
307 return DisplayHandle(display_id)
307 return DisplayHandle(display_id)
308
308
309
309
310 # use * for keyword-only display_id arg
310 # use * for keyword-only display_id arg
311 def update_display(obj, *, display_id, **kwargs):
311 def update_display(obj, *, display_id, **kwargs):
312 """Update an existing display by id
312 """Update an existing display by id
313
313
314 Parameters
314 Parameters
315 ----------
315 ----------
316 obj
316 obj
317 The object with which to update the display
317 The object with which to update the display
318 display_id : keyword-only
318 display_id : keyword-only
319 The id of the display to update
319 The id of the display to update
320
320
321 See Also
321 See Also
322 --------
322 --------
323 :func:`display`
323 :func:`display`
324 """
324 """
325 kwargs['update'] = True
325 kwargs['update'] = True
326 display(obj, display_id=display_id, **kwargs)
326 display(obj, display_id=display_id, **kwargs)
327
327
328
328
329 class DisplayHandle(object):
329 class DisplayHandle(object):
330 """A handle on an updatable display
330 """A handle on an updatable display
331
331
332 Call `.update(obj)` to display a new object.
332 Call `.update(obj)` to display a new object.
333
333
334 Call `.display(obj`) to add a new instance of this display,
334 Call `.display(obj`) to add a new instance of this display,
335 and update existing instances.
335 and update existing instances.
336
336
337 See Also
337 See Also
338 --------
338 --------
339
339
340 :func:`display`, :func:`update_display`
340 :func:`display`, :func:`update_display`
341
341
342 """
342 """
343
343
344 def __init__(self, display_id=None):
344 def __init__(self, display_id=None):
345 if display_id is None:
345 if display_id is None:
346 display_id = _new_id()
346 display_id = _new_id()
347 self.display_id = display_id
347 self.display_id = display_id
348
348
349 def __repr__(self):
349 def __repr__(self):
350 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
350 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
351
351
352 def display(self, obj, **kwargs):
352 def display(self, obj, **kwargs):
353 """Make a new display with my id, updating existing instances.
353 """Make a new display with my id, updating existing instances.
354
354
355 Parameters
355 Parameters
356 ----------
356 ----------
357 obj
357 obj
358 object to display
358 object to display
359 **kwargs
359 **kwargs
360 additional keyword arguments passed to display
360 additional keyword arguments passed to display
361 """
361 """
362 display(obj, display_id=self.display_id, **kwargs)
362 display(obj, display_id=self.display_id, **kwargs)
363
363
364 def update(self, obj, **kwargs):
364 def update(self, obj, **kwargs):
365 """Update existing displays with my id
365 """Update existing displays with my id
366
366
367 Parameters
367 Parameters
368 ----------
368 ----------
369 obj
369 obj
370 object to display
370 object to display
371 **kwargs
371 **kwargs
372 additional keyword arguments passed to update_display
372 additional keyword arguments passed to update_display
373 """
373 """
374 update_display(obj, display_id=self.display_id, **kwargs)
374 update_display(obj, display_id=self.display_id, **kwargs)
375
375
376
376
377 def clear_output(wait=False):
377 def clear_output(wait=False):
378 """Clear the output of the current cell receiving output.
378 """Clear the output of the current cell receiving output.
379
379
380 Parameters
380 Parameters
381 ----------
381 ----------
382 wait : bool [default: false]
382 wait : bool [default: false]
383 Wait to clear the output until new output is available to replace it."""
383 Wait to clear the output until new output is available to replace it."""
384 from IPython.core.interactiveshell import InteractiveShell
384 from IPython.core.interactiveshell import InteractiveShell
385 if InteractiveShell.initialized():
385 if InteractiveShell.initialized():
386 InteractiveShell.instance().display_pub.clear_output(wait)
386 InteractiveShell.instance().display_pub.clear_output(wait)
387 else:
387 else:
388 print('\033[2K\r', end='')
388 print('\033[2K\r', end='')
389 sys.stdout.flush()
389 sys.stdout.flush()
390 print('\033[2K\r', end='')
390 print('\033[2K\r', end='')
391 sys.stderr.flush()
391 sys.stderr.flush()
@@ -1,270 +1,354 b''
1 """Tests for pylab tools module.
1 """Tests for pylab tools module.
2 """
2 """
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 from binascii import a2b_base64
8 from binascii import a2b_base64
9 from io import BytesIO
9 from io import BytesIO
10
10
11 import pytest
11 import pytest
12
12
13 matplotlib = pytest.importorskip("matplotlib")
13 matplotlib = pytest.importorskip("matplotlib")
14 matplotlib.use('Agg')
14 matplotlib.use('Agg')
15 from matplotlib.figure import Figure
15 from matplotlib.figure import Figure
16
16
17 from matplotlib import pyplot as plt
17 from matplotlib import pyplot as plt
18 from matplotlib_inline import backend_inline
18 from matplotlib_inline import backend_inline
19 import numpy as np
19 import numpy as np
20
20
21 from IPython.core.getipython import get_ipython
21 from IPython.core.getipython import get_ipython
22 from IPython.core.interactiveshell import InteractiveShell
22 from IPython.core.interactiveshell import InteractiveShell
23 from IPython.core.display import _PNG, _JPEG
23 from IPython.core.display import _PNG, _JPEG
24 from .. import pylabtools as pt
24 from .. import pylabtools as pt
25
25
26 from IPython.testing import decorators as dec
26 from IPython.testing import decorators as dec
27
27
28
28
29 def test_figure_to_svg():
29 def test_figure_to_svg():
30 # simple empty-figure test
30 # simple empty-figure test
31 fig = plt.figure()
31 fig = plt.figure()
32 assert pt.print_figure(fig, "svg") is None
32 assert pt.print_figure(fig, "svg") is None
33
33
34 plt.close('all')
34 plt.close('all')
35
35
36 # simple check for at least svg-looking output
36 # simple check for at least svg-looking output
37 fig = plt.figure()
37 fig = plt.figure()
38 ax = fig.add_subplot(1,1,1)
38 ax = fig.add_subplot(1,1,1)
39 ax.plot([1,2,3])
39 ax.plot([1,2,3])
40 plt.draw()
40 plt.draw()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 assert "doctype svg" in svg
42 assert "doctype svg" in svg
43
43
44
44
45 def _check_pil_jpeg_bytes():
45 def _check_pil_jpeg_bytes():
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 # PIL's JPEG plugin can't write to BytesIO objects
47 # PIL's JPEG plugin can't write to BytesIO objects
48 # Pillow fixes this
48 # Pillow fixes this
49 from PIL import Image
49 from PIL import Image
50 buf = BytesIO()
50 buf = BytesIO()
51 img = Image.new("RGB", (4,4))
51 img = Image.new("RGB", (4,4))
52 try:
52 try:
53 img.save(buf, 'jpeg')
53 img.save(buf, 'jpeg')
54 except Exception as e:
54 except Exception as e:
55 ename = e.__class__.__name__
55 ename = e.__class__.__name__
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57
57
58 @dec.skip_without("PIL.Image")
58 @dec.skip_without("PIL.Image")
59 def test_figure_to_jpeg():
59 def test_figure_to_jpeg():
60 _check_pil_jpeg_bytes()
60 _check_pil_jpeg_bytes()
61 # simple check for at least jpeg-looking output
61 # simple check for at least jpeg-looking output
62 fig = plt.figure()
62 fig = plt.figure()
63 ax = fig.add_subplot(1,1,1)
63 ax = fig.add_subplot(1,1,1)
64 ax.plot([1,2,3])
64 ax.plot([1,2,3])
65 plt.draw()
65 plt.draw()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 assert jpeg.startswith(_JPEG)
67 assert jpeg.startswith(_JPEG)
68
68
69 def test_retina_figure():
69 def test_retina_figure():
70 # simple empty-figure test
70 # simple empty-figure test
71 fig = plt.figure()
71 fig = plt.figure()
72 assert pt.retina_figure(fig) == None
72 assert pt.retina_figure(fig) == None
73 plt.close('all')
73 plt.close('all')
74
74
75 fig = plt.figure()
75 fig = plt.figure()
76 ax = fig.add_subplot(1,1,1)
76 ax = fig.add_subplot(1,1,1)
77 ax.plot([1,2,3])
77 ax.plot([1,2,3])
78 plt.draw()
78 plt.draw()
79 png, md = pt.retina_figure(fig)
79 png, md = pt.retina_figure(fig)
80 assert png.startswith(_PNG)
80 assert png.startswith(_PNG)
81 assert "width" in md
81 assert "width" in md
82 assert "height" in md
82 assert "height" in md
83
83
84
84
85 _fmt_mime_map = {
85 _fmt_mime_map = {
86 'png': 'image/png',
86 'png': 'image/png',
87 'jpeg': 'image/jpeg',
87 'jpeg': 'image/jpeg',
88 'pdf': 'application/pdf',
88 'pdf': 'application/pdf',
89 'retina': 'image/png',
89 'retina': 'image/png',
90 'svg': 'image/svg+xml',
90 'svg': 'image/svg+xml',
91 }
91 }
92
92
93 def test_select_figure_formats_str():
93 def test_select_figure_formats_str():
94 ip = get_ipython()
94 ip = get_ipython()
95 for fmt, active_mime in _fmt_mime_map.items():
95 for fmt, active_mime in _fmt_mime_map.items():
96 pt.select_figure_formats(ip, fmt)
96 pt.select_figure_formats(ip, fmt)
97 for mime, f in ip.display_formatter.formatters.items():
97 for mime, f in ip.display_formatter.formatters.items():
98 if mime == active_mime:
98 if mime == active_mime:
99 assert Figure in f
99 assert Figure in f
100 else:
100 else:
101 assert Figure not in f
101 assert Figure not in f
102
102
103 def test_select_figure_formats_kwargs():
103 def test_select_figure_formats_kwargs():
104 ip = get_ipython()
104 ip = get_ipython()
105 kwargs = dict(bbox_inches="tight")
105 kwargs = dict(bbox_inches="tight")
106 pt.select_figure_formats(ip, "png", **kwargs)
106 pt.select_figure_formats(ip, "png", **kwargs)
107 formatter = ip.display_formatter.formatters["image/png"]
107 formatter = ip.display_formatter.formatters["image/png"]
108 f = formatter.lookup_by_type(Figure)
108 f = formatter.lookup_by_type(Figure)
109 cell = f.keywords
109 cell = f.keywords
110 expected = kwargs
110 expected = kwargs
111 expected["base64"] = True
111 expected["base64"] = True
112 expected["fmt"] = "png"
112 expected["fmt"] = "png"
113 assert cell == expected
113 assert cell == expected
114
114
115 # check that the formatter doesn't raise
115 # check that the formatter doesn't raise
116 fig = plt.figure()
116 fig = plt.figure()
117 ax = fig.add_subplot(1,1,1)
117 ax = fig.add_subplot(1,1,1)
118 ax.plot([1,2,3])
118 ax.plot([1,2,3])
119 plt.draw()
119 plt.draw()
120 formatter.enabled = True
120 formatter.enabled = True
121 png = formatter(fig)
121 png = formatter(fig)
122 assert isinstance(png, str)
122 assert isinstance(png, str)
123 png_bytes = a2b_base64(png)
123 png_bytes = a2b_base64(png)
124 assert png_bytes.startswith(_PNG)
124 assert png_bytes.startswith(_PNG)
125
125
126 def test_select_figure_formats_set():
126 def test_select_figure_formats_set():
127 ip = get_ipython()
127 ip = get_ipython()
128 for fmts in [
128 for fmts in [
129 {'png', 'svg'},
129 {'png', 'svg'},
130 ['png'],
130 ['png'],
131 ('jpeg', 'pdf', 'retina'),
131 ('jpeg', 'pdf', 'retina'),
132 {'svg'},
132 {'svg'},
133 ]:
133 ]:
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 pt.select_figure_formats(ip, fmts)
135 pt.select_figure_formats(ip, fmts)
136 for mime, f in ip.display_formatter.formatters.items():
136 for mime, f in ip.display_formatter.formatters.items():
137 if mime in active_mimes:
137 if mime in active_mimes:
138 assert Figure in f
138 assert Figure in f
139 else:
139 else:
140 assert Figure not in f
140 assert Figure not in f
141
141
142 def test_select_figure_formats_bad():
142 def test_select_figure_formats_bad():
143 ip = get_ipython()
143 ip = get_ipython()
144 with pytest.raises(ValueError):
144 with pytest.raises(ValueError):
145 pt.select_figure_formats(ip, 'foo')
145 pt.select_figure_formats(ip, 'foo')
146 with pytest.raises(ValueError):
146 with pytest.raises(ValueError):
147 pt.select_figure_formats(ip, {'png', 'foo'})
147 pt.select_figure_formats(ip, {'png', 'foo'})
148 with pytest.raises(ValueError):
148 with pytest.raises(ValueError):
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150
150
151 def test_import_pylab():
151 def test_import_pylab():
152 ns = {}
152 ns = {}
153 pt.import_pylab(ns, import_all=False)
153 pt.import_pylab(ns, import_all=False)
154 assert "plt" in ns
154 assert "plt" in ns
155 assert ns["np"] == np
155 assert ns["np"] == np
156
156
157
157
158 class TestPylabSwitch(object):
158 class TestPylabSwitch(object):
159 class Shell(InteractiveShell):
159 class Shell(InteractiveShell):
160 def init_history(self):
160 def init_history(self):
161 """Sets up the command history, and starts regular autosaves."""
161 """Sets up the command history, and starts regular autosaves."""
162 self.config.HistoryManager.hist_file = ":memory:"
162 self.config.HistoryManager.hist_file = ":memory:"
163 super().init_history()
163 super().init_history()
164
164
165 def enable_gui(self, gui):
165 def enable_gui(self, gui):
166 pass
166 pass
167
167
168 def setup(self):
168 def setup(self):
169 import matplotlib
169 import matplotlib
170 def act_mpl(backend):
170 def act_mpl(backend):
171 matplotlib.rcParams['backend'] = backend
171 matplotlib.rcParams['backend'] = backend
172
172
173 # Save rcParams since they get modified
173 # Save rcParams since they get modified
174 self._saved_rcParams = matplotlib.rcParams
174 self._saved_rcParams = matplotlib.rcParams
175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
176 matplotlib.rcParams = dict(backend="QtAgg")
176 matplotlib.rcParams = dict(backend="QtAgg")
177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
177 matplotlib.rcParamsOrig = dict(backend="QtAgg")
178
178
179 # Mock out functions
179 # Mock out functions
180 self._save_am = pt.activate_matplotlib
180 self._save_am = pt.activate_matplotlib
181 pt.activate_matplotlib = act_mpl
181 pt.activate_matplotlib = act_mpl
182 self._save_ip = pt.import_pylab
182 self._save_ip = pt.import_pylab
183 pt.import_pylab = lambda *a,**kw:None
183 pt.import_pylab = lambda *a,**kw:None
184 self._save_cis = backend_inline.configure_inline_support
184 self._save_cis = backend_inline.configure_inline_support
185 backend_inline.configure_inline_support = lambda *a, **kw: None
185 backend_inline.configure_inline_support = lambda *a, **kw: None
186
186
187 def teardown(self):
187 def teardown(self):
188 pt.activate_matplotlib = self._save_am
188 pt.activate_matplotlib = self._save_am
189 pt.import_pylab = self._save_ip
189 pt.import_pylab = self._save_ip
190 backend_inline.configure_inline_support = self._save_cis
190 backend_inline.configure_inline_support = self._save_cis
191 import matplotlib
191 import matplotlib
192 matplotlib.rcParams = self._saved_rcParams
192 matplotlib.rcParams = self._saved_rcParams
193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
194
194
195 def test_qt(self):
195 def test_qt(self):
196 s = self.Shell()
196 s = self.Shell()
197 gui, backend = s.enable_matplotlib(None)
197 gui, backend = s.enable_matplotlib(None)
198 assert gui == "qt"
198 assert gui == "qt"
199 assert s.pylab_gui_select == "qt"
199 assert s.pylab_gui_select == "qt"
200
200
201 gui, backend = s.enable_matplotlib("inline")
201 gui, backend = s.enable_matplotlib("inline")
202 assert gui is None
202 assert gui is None
203 assert s.pylab_gui_select == "qt"
203 assert s.pylab_gui_select == "qt"
204
204
205 gui, backend = s.enable_matplotlib("qt")
205 gui, backend = s.enable_matplotlib("qt")
206 assert gui == "qt"
206 assert gui == "qt"
207 assert s.pylab_gui_select == "qt"
207 assert s.pylab_gui_select == "qt"
208
208
209 gui, backend = s.enable_matplotlib("inline")
209 gui, backend = s.enable_matplotlib("inline")
210 assert gui is None
210 assert gui is None
211 assert s.pylab_gui_select == "qt"
211 assert s.pylab_gui_select == "qt"
212
212
213 gui, backend = s.enable_matplotlib()
213 gui, backend = s.enable_matplotlib()
214 assert gui == "qt"
214 assert gui == "qt"
215 assert s.pylab_gui_select == "qt"
215 assert s.pylab_gui_select == "qt"
216
216
217 def test_inline(self):
217 def test_inline(self):
218 s = self.Shell()
218 s = self.Shell()
219 gui, backend = s.enable_matplotlib("inline")
219 gui, backend = s.enable_matplotlib("inline")
220 assert gui is None
220 assert gui is None
221 assert s.pylab_gui_select == None
221 assert s.pylab_gui_select == None
222
222
223 gui, backend = s.enable_matplotlib("inline")
223 gui, backend = s.enable_matplotlib("inline")
224 assert gui is None
224 assert gui is None
225 assert s.pylab_gui_select == None
225 assert s.pylab_gui_select == None
226
226
227 gui, backend = s.enable_matplotlib("qt")
227 gui, backend = s.enable_matplotlib("qt")
228 assert gui == "qt"
228 assert gui == "qt"
229 assert s.pylab_gui_select == "qt"
229 assert s.pylab_gui_select == "qt"
230
230
231 def test_inline_twice(self):
231 def test_inline_twice(self):
232 "Using '%matplotlib inline' twice should not reset formatters"
232 "Using '%matplotlib inline' twice should not reset formatters"
233
233
234 ip = self.Shell()
234 ip = self.Shell()
235 gui, backend = ip.enable_matplotlib("inline")
235 gui, backend = ip.enable_matplotlib("inline")
236 assert gui is None
236 assert gui is None
237
237
238 fmts = {'png'}
238 fmts = {'png'}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
239 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 pt.select_figure_formats(ip, fmts)
240 pt.select_figure_formats(ip, fmts)
241
241
242 gui, backend = ip.enable_matplotlib("inline")
242 gui, backend = ip.enable_matplotlib("inline")
243 assert gui is None
243 assert gui is None
244
244
245 for mime, f in ip.display_formatter.formatters.items():
245 for mime, f in ip.display_formatter.formatters.items():
246 if mime in active_mimes:
246 if mime in active_mimes:
247 assert Figure in f
247 assert Figure in f
248 else:
248 else:
249 assert Figure not in f
249 assert Figure not in f
250
250
251 def test_qt_gtk(self):
251 def test_qt_gtk(self):
252 s = self.Shell()
252 s = self.Shell()
253 gui, backend = s.enable_matplotlib("qt")
253 gui, backend = s.enable_matplotlib("qt")
254 assert gui == "qt"
254 assert gui == "qt"
255 assert s.pylab_gui_select == "qt"
255 assert s.pylab_gui_select == "qt"
256
256
257 gui, backend = s.enable_matplotlib("gtk3")
257 gui, backend = s.enable_matplotlib("gtk3")
258 assert gui == "qt"
258 assert gui == "qt"
259 assert s.pylab_gui_select == "qt"
259 assert s.pylab_gui_select == "qt"
260
260
261
261
262 def test_no_gui_backends():
262 def test_no_gui_backends():
263 for k in ['agg', 'svg', 'pdf', 'ps']:
263 for k in ['agg', 'svg', 'pdf', 'ps']:
264 assert k not in pt.backend2gui
264 assert k not in pt.backend2gui
265
265
266
266
267 def test_figure_no_canvas():
267 def test_figure_no_canvas():
268 fig = Figure()
268 fig = Figure()
269 fig.canvas = None
269 fig.canvas = None
270 pt.print_figure(fig)
270 pt.print_figure(fig)
271
272
273 @pytest.mark.parametrize(
274 "name, expected_gui, expected_backend",
275 [
276 # name is gui
277 ("gtk3", "gtk3", "gtk3agg"),
278 ("gtk4", "gtk4", "gtk4agg"),
279 ("headless", "headless", "agg"),
280 ("osx", "osx", "macosx"),
281 ("qt", "qt", "qtagg"),
282 ("qt5", "qt5", "qt5agg"),
283 ("qt6", "qt6", "qt6agg"),
284 ("tk", "tk", "tkagg"),
285 ("wx", "wx", "wxagg"),
286 # name is backend
287 ("agg", None, "agg"),
288 ("cairo", None, "cairo"),
289 ("pdf", None, "pdf"),
290 ("ps", None, "ps"),
291 ("svg", None, "svg"),
292 ("template", None, "template"),
293 ("gtk3agg", "gtk3", "gtk3agg"),
294 ("gtk3cairo", "gtk3", "gtk3cairo"),
295 ("gtk4agg", "gtk4", "gtk4agg"),
296 ("gtk4cairo", "gtk4", "gtk4cairo"),
297 ("macosx", "osx", "macosx"),
298 ("nbagg", "nbagg", "nbagg"),
299 ("notebook", "nbagg", "notebook"),
300 ("qtagg", "qt", "qtagg"),
301 ("qtcairo", "qt", "qtcairo"),
302 ("qt5agg", "qt5", "qt5agg"),
303 ("qt5cairo", "qt5", "qt5cairo"),
304 ("qt6agg", "qt", "qt6agg"),
305 ("qt6cairo", "qt", "qt6cairo"),
306 ("tkagg", "tk", "tkagg"),
307 ("tkcairo", "tk", "tkcairo"),
308 ("webagg", "webagg", "webagg"),
309 ("wxagg", "wx", "wxagg"),
310 ("wxcairo", "wx", "wxcairo"),
311 ],
312 )
313 def test_backend_builtin(name, expected_gui, expected_backend):
314 # Test correct identification of Matplotlib built-in backends without importing and using them,
315 # otherwise we would need to ensure all the complex dependencies such as windowing toolkits are
316 # installed.
317
318 mpl_manages_backends = pt._matplotlib_manages_backends()
319 if not mpl_manages_backends:
320 # Backends not supported before _matplotlib_manages_backends or supported
321 # but with different expected_gui or expected_backend.
322 if (
323 name.endswith("agg")
324 or name.endswith("cairo")
325 or name in ("headless", "macosx", "pdf", "ps", "svg", "template")
326 ):
327 pytest.skip()
328 elif name == "qt6":
329 expected_backend = "qtagg"
330 elif name == "notebook":
331 expected_backend, expected_gui = expected_gui, expected_backend
332
333 gui, backend = pt.find_gui_and_backend(name)
334 if not mpl_manages_backends:
335 gui = gui.lower() if gui else None
336 backend = backend.lower() if backend else None
337 assert gui == expected_gui
338 assert backend == expected_backend
339
340
341 def test_backend_entry_point():
342 gui, backend = pt.find_gui_and_backend("inline")
343 assert gui is None
344 expected_backend = (
345 "inline"
346 if pt._matplotlib_manages_backends()
347 else "module://matplotlib_inline.backend_inline"
348 )
349 assert backend == expected_backend
350
351
352 def test_backend_unknown():
353 with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError):
354 pt.find_gui_and_backend("name-does-not-exist")
@@ -1,426 +1,426 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An embedded IPython shell.
3 An embedded IPython shell.
4 """
4 """
5 # Copyright (c) IPython Development Team.
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7
7
8
8
9 import sys
9 import sys
10 import warnings
10 import warnings
11
11
12 from IPython.core import ultratb, compilerop
12 from IPython.core import ultratb, compilerop
13 from IPython.core import magic_arguments
13 from IPython.core import magic_arguments
14 from IPython.core.magic import Magics, magics_class, line_magic
14 from IPython.core.magic import Magics, magics_class, line_magic
15 from IPython.core.interactiveshell import DummyMod, InteractiveShell
15 from IPython.core.interactiveshell import DummyMod, InteractiveShell
16 from IPython.terminal.interactiveshell import TerminalInteractiveShell
16 from IPython.terminal.interactiveshell import TerminalInteractiveShell
17 from IPython.terminal.ipapp import load_default_config
17 from IPython.terminal.ipapp import load_default_config
18
18
19 from traitlets import Bool, CBool, Unicode
19 from traitlets import Bool, CBool, Unicode
20 from IPython.utils.io import ask_yes_no
20 from IPython.utils.io import ask_yes_no
21
21
22 from typing import Set
22 from typing import Set
23
23
24 class KillEmbedded(Exception):pass
24 class KillEmbedded(Exception):pass
25
25
26 # kept for backward compatibility as IPython 6 was released with
26 # kept for backward compatibility as IPython 6 was released with
27 # the typo. See https://github.com/ipython/ipython/pull/10706
27 # the typo. See https://github.com/ipython/ipython/pull/10706
28 KillEmbeded = KillEmbedded
28 KillEmbeded = KillEmbedded
29
29
30 # This is an additional magic that is exposed in embedded shells.
30 # This is an additional magic that is exposed in embedded shells.
31 @magics_class
31 @magics_class
32 class EmbeddedMagics(Magics):
32 class EmbeddedMagics(Magics):
33
33
34 @line_magic
34 @line_magic
35 @magic_arguments.magic_arguments()
35 @magic_arguments.magic_arguments()
36 @magic_arguments.argument('-i', '--instance', action='store_true',
36 @magic_arguments.argument('-i', '--instance', action='store_true',
37 help='Kill instance instead of call location')
37 help='Kill instance instead of call location')
38 @magic_arguments.argument('-x', '--exit', action='store_true',
38 @magic_arguments.argument('-x', '--exit', action='store_true',
39 help='Also exit the current session')
39 help='Also exit the current session')
40 @magic_arguments.argument('-y', '--yes', action='store_true',
40 @magic_arguments.argument('-y', '--yes', action='store_true',
41 help='Do not ask confirmation')
41 help='Do not ask confirmation')
42 def kill_embedded(self, parameter_s=''):
42 def kill_embedded(self, parameter_s=''):
43 """%kill_embedded : deactivate for good the current embedded IPython
43 """%kill_embedded : deactivate for good the current embedded IPython
44
44
45 This function (after asking for confirmation) sets an internal flag so
45 This function (after asking for confirmation) sets an internal flag so
46 that an embedded IPython will never activate again for the given call
46 that an embedded IPython will never activate again for the given call
47 location. This is useful to permanently disable a shell that is being
47 location. This is useful to permanently disable a shell that is being
48 called inside a loop: once you've figured out what you needed from it,
48 called inside a loop: once you've figured out what you needed from it,
49 you may then kill it and the program will then continue to run without
49 you may then kill it and the program will then continue to run without
50 the interactive shell interfering again.
50 the interactive shell interfering again.
51
51
52 Kill Instance Option:
52 Kill Instance Option:
53
53
54 If for some reasons you need to kill the location where the instance
54 If for some reasons you need to kill the location where the instance
55 is created and not called, for example if you create a single
55 is created and not called, for example if you create a single
56 instance in one place and debug in many locations, you can use the
56 instance in one place and debug in many locations, you can use the
57 ``--instance`` option to kill this specific instance. Like for the
57 ``--instance`` option to kill this specific instance. Like for the
58 ``call location`` killing an "instance" should work even if it is
58 ``call location`` killing an "instance" should work even if it is
59 recreated within a loop.
59 recreated within a loop.
60
60
61 .. note::
61 .. note::
62
62
63 This was the default behavior before IPython 5.2
63 This was the default behavior before IPython 5.2
64
64
65 """
65 """
66
66
67 args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s)
67 args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s)
68 print(args)
68 print(args)
69 if args.instance:
69 if args.instance:
70 # let no ask
70 # let no ask
71 if not args.yes:
71 if not args.yes:
72 kill = ask_yes_no(
72 kill = ask_yes_no(
73 "Are you sure you want to kill this embedded instance? [y/N] ", 'n')
73 "Are you sure you want to kill this embedded instance? [y/N] ", 'n')
74 else:
74 else:
75 kill = True
75 kill = True
76 if kill:
76 if kill:
77 self.shell._disable_init_location()
77 self.shell._disable_init_location()
78 print("This embedded IPython instance will not reactivate anymore "
78 print("This embedded IPython instance will not reactivate anymore "
79 "once you exit.")
79 "once you exit.")
80 else:
80 else:
81 if not args.yes:
81 if not args.yes:
82 kill = ask_yes_no(
82 kill = ask_yes_no(
83 "Are you sure you want to kill this embedded call_location? [y/N] ", 'n')
83 "Are you sure you want to kill this embedded call_location? [y/N] ", 'n')
84 else:
84 else:
85 kill = True
85 kill = True
86 if kill:
86 if kill:
87 self.shell.embedded_active = False
87 self.shell.embedded_active = False
88 print("This embedded IPython call location will not reactivate anymore "
88 print("This embedded IPython call location will not reactivate anymore "
89 "once you exit.")
89 "once you exit.")
90
90
91 if args.exit:
91 if args.exit:
92 # Ask-exit does not really ask, it just set internals flags to exit
92 # Ask-exit does not really ask, it just set internals flags to exit
93 # on next loop.
93 # on next loop.
94 self.shell.ask_exit()
94 self.shell.ask_exit()
95
95
96
96
97 @line_magic
97 @line_magic
98 def exit_raise(self, parameter_s=''):
98 def exit_raise(self, parameter_s=''):
99 """%exit_raise Make the current embedded kernel exit and raise and exception.
99 """%exit_raise Make the current embedded kernel exit and raise and exception.
100
100
101 This function sets an internal flag so that an embedded IPython will
101 This function sets an internal flag so that an embedded IPython will
102 raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is
102 raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is
103 useful to permanently exit a loop that create IPython embed instance.
103 useful to permanently exit a loop that create IPython embed instance.
104 """
104 """
105
105
106 self.shell.should_raise = True
106 self.shell.should_raise = True
107 self.shell.ask_exit()
107 self.shell.ask_exit()
108
108
109
109
110 class _Sentinel:
110 class _Sentinel:
111 def __init__(self, repr):
111 def __init__(self, repr):
112 assert isinstance(repr, str)
112 assert isinstance(repr, str)
113 self.repr = repr
113 self.repr = repr
114
114
115 def __repr__(self):
115 def __repr__(self):
116 return repr
116 return repr
117
117
118
118
119 class InteractiveShellEmbed(TerminalInteractiveShell):
119 class InteractiveShellEmbed(TerminalInteractiveShell):
120
120
121 dummy_mode = Bool(False)
121 dummy_mode = Bool(False)
122 exit_msg = Unicode('')
122 exit_msg = Unicode('')
123 embedded = CBool(True)
123 embedded = CBool(True)
124 should_raise = CBool(False)
124 should_raise = CBool(False)
125 # Like the base class display_banner is not configurable, but here it
125 # Like the base class display_banner is not configurable, but here it
126 # is True by default.
126 # is True by default.
127 display_banner = CBool(True)
127 display_banner = CBool(True)
128 exit_msg = Unicode()
128 exit_msg = Unicode()
129
129
130 # When embedding, by default we don't change the terminal title
130 # When embedding, by default we don't change the terminal title
131 term_title = Bool(False,
131 term_title = Bool(False,
132 help="Automatically set the terminal title"
132 help="Automatically set the terminal title"
133 ).tag(config=True)
133 ).tag(config=True)
134
134
135 _inactive_locations: Set[str] = set()
135 _inactive_locations: Set[str] = set()
136
136
137 def _disable_init_location(self):
137 def _disable_init_location(self):
138 """Disable the current Instance creation location"""
138 """Disable the current Instance creation location"""
139 InteractiveShellEmbed._inactive_locations.add(self._init_location_id)
139 InteractiveShellEmbed._inactive_locations.add(self._init_location_id)
140
140
141 @property
141 @property
142 def embedded_active(self):
142 def embedded_active(self):
143 return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\
143 return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\
144 and (self._init_location_id not in InteractiveShellEmbed._inactive_locations)
144 and (self._init_location_id not in InteractiveShellEmbed._inactive_locations)
145
145
146 @embedded_active.setter
146 @embedded_active.setter
147 def embedded_active(self, value):
147 def embedded_active(self, value):
148 if value:
148 if value:
149 InteractiveShellEmbed._inactive_locations.discard(
149 InteractiveShellEmbed._inactive_locations.discard(
150 self._call_location_id)
150 self._call_location_id)
151 InteractiveShellEmbed._inactive_locations.discard(
151 InteractiveShellEmbed._inactive_locations.discard(
152 self._init_location_id)
152 self._init_location_id)
153 else:
153 else:
154 InteractiveShellEmbed._inactive_locations.add(
154 InteractiveShellEmbed._inactive_locations.add(
155 self._call_location_id)
155 self._call_location_id)
156
156
157 def __init__(self, **kw):
157 def __init__(self, **kw):
158 assert (
158 assert (
159 "user_global_ns" not in kw
159 "user_global_ns" not in kw
160 ), "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0."
160 ), "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0."
161 # temporary fix for https://github.com/ipython/ipython/issues/14164
161 # temporary fix for https://github.com/ipython/ipython/issues/14164
162 cls = type(self)
162 cls = type(self)
163 if cls._instance is None:
163 if cls._instance is None:
164 for subclass in cls._walk_mro():
164 for subclass in cls._walk_mro():
165 subclass._instance = self
165 subclass._instance = self
166 cls._instance = self
166 cls._instance = self
167
167
168 clid = kw.pop('_init_location_id', None)
168 clid = kw.pop('_init_location_id', None)
169 if not clid:
169 if not clid:
170 frame = sys._getframe(1)
170 frame = sys._getframe(1)
171 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
171 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
172 self._init_location_id = clid
172 self._init_location_id = clid
173
173
174 super(InteractiveShellEmbed,self).__init__(**kw)
174 super(InteractiveShellEmbed,self).__init__(**kw)
175
175
176 # don't use the ipython crash handler so that user exceptions aren't
176 # don't use the ipython crash handler so that user exceptions aren't
177 # trapped
177 # trapped
178 sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors,
178 sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors,
179 mode=self.xmode,
179 mode=self.xmode,
180 call_pdb=self.pdb)
180 call_pdb=self.pdb)
181
181
182 def init_sys_modules(self):
182 def init_sys_modules(self):
183 """
183 """
184 Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing.
184 Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing.
185 """
185 """
186 pass
186 pass
187
187
188 def init_magics(self):
188 def init_magics(self):
189 super(InteractiveShellEmbed, self).init_magics()
189 super(InteractiveShellEmbed, self).init_magics()
190 self.register_magics(EmbeddedMagics)
190 self.register_magics(EmbeddedMagics)
191
191
192 def __call__(
192 def __call__(
193 self,
193 self,
194 header="",
194 header="",
195 local_ns=None,
195 local_ns=None,
196 module=None,
196 module=None,
197 dummy=None,
197 dummy=None,
198 stack_depth=1,
198 stack_depth=1,
199 compile_flags=None,
199 compile_flags=None,
200 **kw
200 **kw,
201 ):
201 ):
202 """Activate the interactive interpreter.
202 """Activate the interactive interpreter.
203
203
204 __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start
204 __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start
205 the interpreter shell with the given local and global namespaces, and
205 the interpreter shell with the given local and global namespaces, and
206 optionally print a header string at startup.
206 optionally print a header string at startup.
207
207
208 The shell can be globally activated/deactivated using the
208 The shell can be globally activated/deactivated using the
209 dummy_mode attribute. This allows you to turn off a shell used
209 dummy_mode attribute. This allows you to turn off a shell used
210 for debugging globally.
210 for debugging globally.
211
211
212 However, *each* time you call the shell you can override the current
212 However, *each* time you call the shell you can override the current
213 state of dummy_mode with the optional keyword parameter 'dummy'. For
213 state of dummy_mode with the optional keyword parameter 'dummy'. For
214 example, if you set dummy mode on with IPShell.dummy_mode = True, you
214 example, if you set dummy mode on with IPShell.dummy_mode = True, you
215 can still have a specific call work by making it as IPShell(dummy=False).
215 can still have a specific call work by making it as IPShell(dummy=False).
216 """
216 """
217
217
218 # we are called, set the underlying interactiveshell not to exit.
218 # we are called, set the underlying interactiveshell not to exit.
219 self.keep_running = True
219 self.keep_running = True
220
220
221 # If the user has turned it off, go away
221 # If the user has turned it off, go away
222 clid = kw.pop('_call_location_id', None)
222 clid = kw.pop('_call_location_id', None)
223 if not clid:
223 if not clid:
224 frame = sys._getframe(1)
224 frame = sys._getframe(1)
225 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
225 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
226 self._call_location_id = clid
226 self._call_location_id = clid
227
227
228 if not self.embedded_active:
228 if not self.embedded_active:
229 return
229 return
230
230
231 # Normal exits from interactive mode set this flag, so the shell can't
231 # Normal exits from interactive mode set this flag, so the shell can't
232 # re-enter (it checks this variable at the start of interactive mode).
232 # re-enter (it checks this variable at the start of interactive mode).
233 self.exit_now = False
233 self.exit_now = False
234
234
235 # Allow the dummy parameter to override the global __dummy_mode
235 # Allow the dummy parameter to override the global __dummy_mode
236 if dummy or (dummy != 0 and self.dummy_mode):
236 if dummy or (dummy != 0 and self.dummy_mode):
237 return
237 return
238
238
239 # self.banner is auto computed
239 # self.banner is auto computed
240 if header:
240 if header:
241 self.old_banner2 = self.banner2
241 self.old_banner2 = self.banner2
242 self.banner2 = self.banner2 + '\n' + header + '\n'
242 self.banner2 = self.banner2 + '\n' + header + '\n'
243 else:
243 else:
244 self.old_banner2 = ''
244 self.old_banner2 = ''
245
245
246 if self.display_banner:
246 if self.display_banner:
247 self.show_banner()
247 self.show_banner()
248
248
249 # Call the embedding code with a stack depth of 1 so it can skip over
249 # Call the embedding code with a stack depth of 1 so it can skip over
250 # our call and get the original caller's namespaces.
250 # our call and get the original caller's namespaces.
251 self.mainloop(
251 self.mainloop(
252 local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags
252 local_ns, module, stack_depth=stack_depth, compile_flags=compile_flags
253 )
253 )
254
254
255 self.banner2 = self.old_banner2
255 self.banner2 = self.old_banner2
256
256
257 if self.exit_msg is not None:
257 if self.exit_msg is not None:
258 print(self.exit_msg)
258 print(self.exit_msg)
259
259
260 if self.should_raise:
260 if self.should_raise:
261 raise KillEmbedded('Embedded IPython raising error, as user requested.')
261 raise KillEmbedded('Embedded IPython raising error, as user requested.')
262
262
263 def mainloop(
263 def mainloop(
264 self,
264 self,
265 local_ns=None,
265 local_ns=None,
266 module=None,
266 module=None,
267 stack_depth=0,
267 stack_depth=0,
268 compile_flags=None,
268 compile_flags=None,
269 ):
269 ):
270 """Embeds IPython into a running python program.
270 """Embeds IPython into a running python program.
271
271
272 Parameters
272 Parameters
273 ----------
273 ----------
274 local_ns, module
274 local_ns, module
275 Working local namespace (a dict) and module (a module or similar
275 Working local namespace (a dict) and module (a module or similar
276 object). If given as None, they are automatically taken from the scope
276 object). If given as None, they are automatically taken from the scope
277 where the shell was called, so that program variables become visible.
277 where the shell was called, so that program variables become visible.
278 stack_depth : int
278 stack_depth : int
279 How many levels in the stack to go to looking for namespaces (when
279 How many levels in the stack to go to looking for namespaces (when
280 local_ns or module is None). This allows an intermediate caller to
280 local_ns or module is None). This allows an intermediate caller to
281 make sure that this function gets the namespace from the intended
281 make sure that this function gets the namespace from the intended
282 level in the stack. By default (0) it will get its locals and globals
282 level in the stack. By default (0) it will get its locals and globals
283 from the immediate caller.
283 from the immediate caller.
284 compile_flags
284 compile_flags
285 A bit field identifying the __future__ features
285 A bit field identifying the __future__ features
286 that are enabled, as passed to the builtin :func:`compile` function.
286 that are enabled, as passed to the builtin :func:`compile` function.
287 If given as None, they are automatically taken from the scope where
287 If given as None, they are automatically taken from the scope where
288 the shell was called.
288 the shell was called.
289
289
290 """
290 """
291
291
292 # Get locals and globals from caller
292 # Get locals and globals from caller
293 if ((local_ns is None or module is None or compile_flags is None)
293 if ((local_ns is None or module is None or compile_flags is None)
294 and self.default_user_namespaces):
294 and self.default_user_namespaces):
295 call_frame = sys._getframe(stack_depth).f_back
295 call_frame = sys._getframe(stack_depth).f_back
296
296
297 if local_ns is None:
297 if local_ns is None:
298 local_ns = call_frame.f_locals
298 local_ns = call_frame.f_locals
299 if module is None:
299 if module is None:
300 global_ns = call_frame.f_globals
300 global_ns = call_frame.f_globals
301 try:
301 try:
302 module = sys.modules[global_ns['__name__']]
302 module = sys.modules[global_ns['__name__']]
303 except KeyError:
303 except KeyError:
304 warnings.warn("Failed to get module %s" % \
304 warnings.warn("Failed to get module %s" % \
305 global_ns.get('__name__', 'unknown module')
305 global_ns.get('__name__', 'unknown module')
306 )
306 )
307 module = DummyMod()
307 module = DummyMod()
308 module.__dict__ = global_ns
308 module.__dict__ = global_ns
309 if compile_flags is None:
309 if compile_flags is None:
310 compile_flags = (call_frame.f_code.co_flags &
310 compile_flags = (call_frame.f_code.co_flags &
311 compilerop.PyCF_MASK)
311 compilerop.PyCF_MASK)
312
312
313 # Save original namespace and module so we can restore them after
313 # Save original namespace and module so we can restore them after
314 # embedding; otherwise the shell doesn't shut down correctly.
314 # embedding; otherwise the shell doesn't shut down correctly.
315 orig_user_module = self.user_module
315 orig_user_module = self.user_module
316 orig_user_ns = self.user_ns
316 orig_user_ns = self.user_ns
317 orig_compile_flags = self.compile.flags
317 orig_compile_flags = self.compile.flags
318
318
319 # Update namespaces and fire up interpreter
319 # Update namespaces and fire up interpreter
320
320
321 # The global one is easy, we can just throw it in
321 # The global one is easy, we can just throw it in
322 if module is not None:
322 if module is not None:
323 self.user_module = module
323 self.user_module = module
324
324
325 # But the user/local one is tricky: ipython needs it to store internal
325 # But the user/local one is tricky: ipython needs it to store internal
326 # data, but we also need the locals. We'll throw our hidden variables
326 # data, but we also need the locals. We'll throw our hidden variables
327 # like _ih and get_ipython() into the local namespace, but delete them
327 # like _ih and get_ipython() into the local namespace, but delete them
328 # later.
328 # later.
329 if local_ns is not None:
329 if local_ns is not None:
330 reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()}
330 reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()}
331 self.user_ns = reentrant_local_ns
331 self.user_ns = reentrant_local_ns
332 self.init_user_ns()
332 self.init_user_ns()
333
333
334 # Compiler flags
334 # Compiler flags
335 if compile_flags is not None:
335 if compile_flags is not None:
336 self.compile.flags = compile_flags
336 self.compile.flags = compile_flags
337
337
338 # make sure the tab-completer has the correct frame information, so it
338 # make sure the tab-completer has the correct frame information, so it
339 # actually completes using the frame's locals/globals
339 # actually completes using the frame's locals/globals
340 self.set_completer_frame()
340 self.set_completer_frame()
341
341
342 with self.builtin_trap, self.display_trap:
342 with self.builtin_trap, self.display_trap:
343 self.interact()
343 self.interact()
344
344
345 # now, purge out the local namespace of IPython's hidden variables.
345 # now, purge out the local namespace of IPython's hidden variables.
346 if local_ns is not None:
346 if local_ns is not None:
347 local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()})
347 local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()})
348
348
349
349
350 # Restore original namespace so shell can shut down when we exit.
350 # Restore original namespace so shell can shut down when we exit.
351 self.user_module = orig_user_module
351 self.user_module = orig_user_module
352 self.user_ns = orig_user_ns
352 self.user_ns = orig_user_ns
353 self.compile.flags = orig_compile_flags
353 self.compile.flags = orig_compile_flags
354
354
355
355
356 def embed(*, header="", compile_flags=None, **kwargs):
356 def embed(*, header="", compile_flags=None, **kwargs):
357 """Call this to embed IPython at the current point in your program.
357 """Call this to embed IPython at the current point in your program.
358
358
359 The first invocation of this will create a :class:`terminal.embed.InteractiveShellEmbed`
359 The first invocation of this will create a :class:`terminal.embed.InteractiveShellEmbed`
360 instance and then call it. Consecutive calls just call the already
360 instance and then call it. Consecutive calls just call the already
361 created instance.
361 created instance.
362
362
363 If you don't want the kernel to initialize the namespace
363 If you don't want the kernel to initialize the namespace
364 from the scope of the surrounding function,
364 from the scope of the surrounding function,
365 and/or you want to load full IPython configuration,
365 and/or you want to load full IPython configuration,
366 you probably want `IPython.start_ipython()` instead.
366 you probably want `IPython.start_ipython()` instead.
367
367
368 Here is a simple example::
368 Here is a simple example::
369
369
370 from IPython import embed
370 from IPython import embed
371 a = 10
371 a = 10
372 b = 20
372 b = 20
373 embed(header='First time')
373 embed(header='First time')
374 c = 30
374 c = 30
375 d = 40
375 d = 40
376 embed()
376 embed()
377
377
378 Parameters
378 Parameters
379 ----------
379 ----------
380
380
381 header : str
381 header : str
382 Optional header string to print at startup.
382 Optional header string to print at startup.
383 compile_flags
383 compile_flags
384 Passed to the `compile_flags` parameter of :py:meth:`terminal.embed.InteractiveShellEmbed.mainloop()`,
384 Passed to the `compile_flags` parameter of :py:meth:`terminal.embed.InteractiveShellEmbed.mainloop()`,
385 which is called when the :class:`terminal.embed.InteractiveShellEmbed` instance is called.
385 which is called when the :class:`terminal.embed.InteractiveShellEmbed` instance is called.
386 **kwargs : various, optional
386 **kwargs : various, optional
387 Any other kwargs will be passed to the :class:`terminal.embed.InteractiveShellEmbed` constructor.
387 Any other kwargs will be passed to the :class:`terminal.embed.InteractiveShellEmbed` constructor.
388 Full customization can be done by passing a traitlets :class:`Config` in as the
388 Full customization can be done by passing a traitlets :class:`Config` in as the
389 `config` argument (see :ref:`configure_start_ipython` and :ref:`terminal_options`).
389 `config` argument (see :ref:`configure_start_ipython` and :ref:`terminal_options`).
390 """
390 """
391 config = kwargs.get('config')
391 config = kwargs.get('config')
392 if config is None:
392 if config is None:
393 config = load_default_config()
393 config = load_default_config()
394 config.InteractiveShellEmbed = config.TerminalInteractiveShell
394 config.InteractiveShellEmbed = config.TerminalInteractiveShell
395 kwargs['config'] = config
395 kwargs['config'] = config
396 using = kwargs.get('using', 'sync')
396 using = kwargs.get('using', 'sync')
397 if using :
397 if using :
398 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}})
398 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}})
399 #save ps1/ps2 if defined
399 #save ps1/ps2 if defined
400 ps1 = None
400 ps1 = None
401 ps2 = None
401 ps2 = None
402 try:
402 try:
403 ps1 = sys.ps1
403 ps1 = sys.ps1
404 ps2 = sys.ps2
404 ps2 = sys.ps2
405 except AttributeError:
405 except AttributeError:
406 pass
406 pass
407 #save previous instance
407 #save previous instance
408 saved_shell_instance = InteractiveShell._instance
408 saved_shell_instance = InteractiveShell._instance
409 if saved_shell_instance is not None:
409 if saved_shell_instance is not None:
410 cls = type(saved_shell_instance)
410 cls = type(saved_shell_instance)
411 cls.clear_instance()
411 cls.clear_instance()
412 frame = sys._getframe(1)
412 frame = sys._getframe(1)
413 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
413 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
414 frame.f_code.co_filename, frame.f_lineno), **kwargs)
414 frame.f_code.co_filename, frame.f_lineno), **kwargs)
415 shell(header=header, stack_depth=2, compile_flags=compile_flags,
415 shell(header=header, stack_depth=2, compile_flags=compile_flags,
416 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
416 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
417 InteractiveShellEmbed.clear_instance()
417 InteractiveShellEmbed.clear_instance()
418 #restore previous instance
418 #restore previous instance
419 if saved_shell_instance is not None:
419 if saved_shell_instance is not None:
420 cls = type(saved_shell_instance)
420 cls = type(saved_shell_instance)
421 cls.clear_instance()
421 cls.clear_instance()
422 for subclass in cls._walk_mro():
422 for subclass in cls._walk_mro():
423 subclass._instance = saved_shell_instance
423 subclass._instance = saved_shell_instance
424 if ps1 is not None:
424 if ps1 is not None:
425 sys.ps1 = ps1
425 sys.ps1 = ps1
426 sys.ps2 = ps2
426 sys.ps2 = ps2
General Comments 0
You need to be logged in to leave comments. Login now