##// END OF EJS Templates
Merge pull request #13419 from Carreau/deprecate-sdd...
Matthias Bussonnier -
r27372:8e6d6c66 merge
parent child Browse files
Show More
@@ -1,374 +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
12
12 __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
13 __all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
13
14
14 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
15 # utility functions
16 # utility functions
16 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
17
18
18
19
19 def _merge(d1, d2):
20 def _merge(d1, d2):
20 """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.
21
22
22 Updates d1 in-place
23 Updates d1 in-place
23 """
24 """
24
25
25 if not isinstance(d2, dict) or not isinstance(d1, dict):
26 if not isinstance(d2, dict) or not isinstance(d1, dict):
26 return d2
27 return d2
27 for key, value in d2.items():
28 for key, value in d2.items():
28 d1[key] = _merge(d1.get(key), value)
29 d1[key] = _merge(d1.get(key), value)
29 return d1
30 return d1
30
31
31
32
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33 # Main functions
34 # Main functions
34 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
35
36
37 class _Sentinel:
38 def __repr__(self):
39 return "<deprecated>"
40
41
42 _sentinel = _Sentinel()
36
43
37 # use * to indicate transient is keyword-only
44 # use * to indicate transient is keyword-only
38 def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs):
45 def publish_display_data(
46 data, metadata=None, source=_sentinel, *, transient=None, **kwargs
47 ):
39 """Publish data and metadata to all frontends.
48 """Publish data and metadata to all frontends.
40
49
41 See the ``display_data`` message in the messaging documentation for
50 See the ``display_data`` message in the messaging documentation for
42 more details about this message type.
51 more details about this message type.
43
52
44 Keys of data and metadata can be any mime-type.
53 Keys of data and metadata can be any mime-type.
45
54
46 Parameters
55 Parameters
47 ----------
56 ----------
48 data : dict
57 data : dict
49 A dictionary having keys that are valid MIME types (like
58 A dictionary having keys that are valid MIME types (like
50 '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
51 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
52 structure. Minimally all data should have the 'text/plain' data,
61 structure. Minimally all data should have the 'text/plain' data,
53 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
54 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
55 representation to use.
64 representation to use.
56 metadata : dict
65 metadata : dict
57 A dictionary for metadata related to the data. This can contain
66 A dictionary for metadata related to the data. This can contain
58 arbitrary key, value pairs that frontends can use to interpret
67 arbitrary key, value pairs that frontends can use to interpret
59 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
60 to specify metadata about particular representations.
69 to specify metadata about particular representations.
61 source : str, deprecated
70 source : str, deprecated
62 Unused.
71 Unused.
63 transient : dict, keyword-only
72 transient : dict, keyword-only
64 A dictionary of transient data, such as display_id.
73 A dictionary of transient data, such as display_id.
65 """
74 """
66 from IPython.core.interactiveshell import InteractiveShell
75 from IPython.core.interactiveshell import InteractiveShell
67
76
77 if source is not _sentinel:
78 warnings.warn(
79 "The `source` parameter emit a deprecation warning since"
80 " IPython 8.0, it had no effects for a long time and will "
81 " be removed in future versions.",
82 DeprecationWarning,
83 stacklevel=2,
84 )
68 display_pub = InteractiveShell.instance().display_pub
85 display_pub = InteractiveShell.instance().display_pub
69
86
70 # only pass transient if supplied,
87 # only pass transient if supplied,
71 # to avoid errors with older ipykernel.
88 # to avoid errors with older ipykernel.
72 # 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.
73 if transient:
90 if transient:
74 kwargs['transient'] = transient
91 kwargs['transient'] = transient
75
92
76 display_pub.publish(
93 display_pub.publish(
77 data=data,
94 data=data,
78 metadata=metadata,
95 metadata=metadata,
79 **kwargs
96 **kwargs
80 )
97 )
81
98
82
99
83 def _new_id():
100 def _new_id():
84 """Generate a new random text id with urandom"""
101 """Generate a new random text id with urandom"""
85 return b2a_hex(os.urandom(16)).decode('ascii')
102 return b2a_hex(os.urandom(16)).decode('ascii')
86
103
87
104
88 def display(
105 def display(
89 *objs,
106 *objs,
90 include=None,
107 include=None,
91 exclude=None,
108 exclude=None,
92 metadata=None,
109 metadata=None,
93 transient=None,
110 transient=None,
94 display_id=None,
111 display_id=None,
95 raw=False,
112 raw=False,
96 clear=False,
113 clear=False,
97 **kwargs
114 **kwargs
98 ):
115 ):
99 """Display a Python object in all frontends.
116 """Display a Python object in all frontends.
100
117
101 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.
102 Frontends can decide which representation is used and how.
119 Frontends can decide which representation is used and how.
103
120
104 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
105 frontends see Jupyter notebook examples with rich display logic.
122 frontends see Jupyter notebook examples with rich display logic.
106
123
107 Parameters
124 Parameters
108 ----------
125 ----------
109 *objs : object
126 *objs : object
110 The Python objects to display.
127 The Python objects to display.
111 raw : bool, optional
128 raw : bool, optional
112 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,
113 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]
114 include : list, tuple or set, optional
131 include : list, tuple or set, optional
115 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
116 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
117 in this list will be computed.
134 in this list will be computed.
118 exclude : list, tuple or set, optional
135 exclude : list, tuple or set, optional
119 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
120 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,
121 except for those included in this argument.
138 except for those included in this argument.
122 metadata : dict, optional
139 metadata : dict, optional
123 A dictionary of metadata to associate with the output.
140 A dictionary of metadata to associate with the output.
124 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
125 representation formats, if they exist.
142 representation formats, if they exist.
126 transient : dict, optional
143 transient : dict, optional
127 A dictionary of transient data to associate with the output.
144 A dictionary of transient data to associate with the output.
128 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).
129 display_id : str, bool optional
146 display_id : str, bool optional
130 Set an id for the display.
147 Set an id for the display.
131 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.
132 If given as `True`, generate a new `display_id`
149 If given as `True`, generate a new `display_id`
133 clear : bool, optional
150 clear : bool, optional
134 Should the output area be cleared before displaying anything? If True,
151 Should the output area be cleared before displaying anything? If True,
135 this will wait for additional output before clearing. [default: False]
152 this will wait for additional output before clearing. [default: False]
136 **kwargs : additional keyword-args, optional
153 **kwargs : additional keyword-args, optional
137 Additional keyword-arguments are passed through to the display publisher.
154 Additional keyword-arguments are passed through to the display publisher.
138
155
139 Returns
156 Returns
140 -------
157 -------
141 handle: DisplayHandle
158 handle: DisplayHandle
142 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`,
143 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
144 (default).
161 (default).
145
162
146 Examples
163 Examples
147 --------
164 --------
148 >>> class Json(object):
165 >>> class Json(object):
149 ... def __init__(self, json):
166 ... def __init__(self, json):
150 ... self.json = json
167 ... self.json = json
151 ... def _repr_pretty_(self, pp, cycle):
168 ... def _repr_pretty_(self, pp, cycle):
152 ... import json
169 ... import json
153 ... pp.text(json.dumps(self.json, indent=2))
170 ... pp.text(json.dumps(self.json, indent=2))
154 ... def __repr__(self):
171 ... def __repr__(self):
155 ... return str(self.json)
172 ... return str(self.json)
156 ...
173 ...
157
174
158 >>> d = Json({1:2, 3: {4:5}})
175 >>> d = Json({1:2, 3: {4:5}})
159
176
160 >>> print(d)
177 >>> print(d)
161 {1: 2, 3: {4: 5}}
178 {1: 2, 3: {4: 5}}
162
179
163 >>> display(d)
180 >>> display(d)
164 {
181 {
165 "1": 2,
182 "1": 2,
166 "3": {
183 "3": {
167 "4": 5
184 "4": 5
168 }
185 }
169 }
186 }
170
187
171 >>> def int_formatter(integer, pp, cycle):
188 >>> def int_formatter(integer, pp, cycle):
172 ... pp.text('I'*integer)
189 ... pp.text('I'*integer)
173
190
174 >>> plain = get_ipython().display_formatter.formatters['text/plain']
191 >>> plain = get_ipython().display_formatter.formatters['text/plain']
175 >>> plain.for_type(int, int_formatter)
192 >>> plain.for_type(int, int_formatter)
176 <function _repr_pprint at 0x...>
193 <function _repr_pprint at 0x...>
177 >>> display(7-5)
194 >>> display(7-5)
178 II
195 II
179
196
180 >>> del plain.type_printers[int]
197 >>> del plain.type_printers[int]
181 >>> display(7-5)
198 >>> display(7-5)
182 2
199 2
183
200
184 See Also
201 See Also
185 --------
202 --------
186 :func:`update_display`
203 :func:`update_display`
187
204
188 Notes
205 Notes
189 -----
206 -----
190 In Python, objects can declare their textual representation using the
207 In Python, objects can declare their textual representation using the
191 `__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
192 other, rich representations including:
209 other, rich representations including:
193
210
194 - HTML
211 - HTML
195 - JSON
212 - JSON
196 - PNG
213 - PNG
197 - JPEG
214 - JPEG
198 - SVG
215 - SVG
199 - LaTeX
216 - LaTeX
200
217
201 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
202 handled by IPython's display system.
219 handled by IPython's display system.
203
220
204 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
205 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
206 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
207 values they must return:
224 values they must return:
208
225
209 - `_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).
210 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
227 - `_repr_json_`: return a JSONable dict, or a tuple (see below).
211 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
228 - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
212 - `_repr_png_`: return raw PNG data, or a tuple (see below).
229 - `_repr_png_`: return raw PNG data, or a tuple (see below).
213 - `_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).
214 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
231 - `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
215 or a tuple (see below).
232 or a tuple (see below).
216 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
233 - `_repr_mimebundle_`: return a full mimebundle containing the mapping
217 from all mimetypes to data.
234 from all mimetypes to data.
218 Use this for any mime-type not listed above.
235 Use this for any mime-type not listed above.
219
236
220 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
221 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
222 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
223 available, then the functions will return the data only.
240 available, then the functions will return the data only.
224
241
225 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
226 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
227 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.
228
245
229 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
230 order to register custom formatters for already existing types
247 order to register custom formatters for already existing types
231 (:ref:`integrating_rich_display`).
248 (:ref:`integrating_rich_display`).
232
249
233 .. versionadded:: 5.4 display available without import
250 .. versionadded:: 5.4 display available without import
234 .. versionadded:: 6.1 display available without import
251 .. versionadded:: 6.1 display available without import
235
252
236 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
237 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
238 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
239 following import at the top of your file::
256 following import at the top of your file::
240
257
241 from IPython.display import display
258 from IPython.display import display
242
259
243 """
260 """
244 from IPython.core.interactiveshell import InteractiveShell
261 from IPython.core.interactiveshell import InteractiveShell
245
262
246 if not InteractiveShell.initialized():
263 if not InteractiveShell.initialized():
247 # Directly print objects.
264 # Directly print objects.
248 print(*objs)
265 print(*objs)
249 return
266 return
250
267
251 if transient is None:
268 if transient is None:
252 transient = {}
269 transient = {}
253 if metadata is None:
270 if metadata is None:
254 metadata={}
271 metadata={}
255 if display_id:
272 if display_id:
256 if display_id is True:
273 if display_id is True:
257 display_id = _new_id()
274 display_id = _new_id()
258 transient['display_id'] = display_id
275 transient['display_id'] = display_id
259 if kwargs.get('update') and 'display_id' not in transient:
276 if kwargs.get('update') and 'display_id' not in transient:
260 raise TypeError('display_id required for update_display')
277 raise TypeError('display_id required for update_display')
261 if transient:
278 if transient:
262 kwargs['transient'] = transient
279 kwargs['transient'] = transient
263
280
264 if not objs and display_id:
281 if not objs and display_id:
265 # 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,
266 # we assume the user wants to insert an empty output that
283 # we assume the user wants to insert an empty output that
267 # can be updated later
284 # can be updated later
268 objs = [{}]
285 objs = [{}]
269 raw = True
286 raw = True
270
287
271 if not raw:
288 if not raw:
272 format = InteractiveShell.instance().display_formatter.format
289 format = InteractiveShell.instance().display_formatter.format
273
290
274 if clear:
291 if clear:
275 clear_output(wait=True)
292 clear_output(wait=True)
276
293
277 for obj in objs:
294 for obj in objs:
278 if raw:
295 if raw:
279 publish_display_data(data=obj, metadata=metadata, **kwargs)
296 publish_display_data(data=obj, metadata=metadata, **kwargs)
280 else:
297 else:
281 format_dict, md_dict = format(obj, include=include, exclude=exclude)
298 format_dict, md_dict = format(obj, include=include, exclude=exclude)
282 if not format_dict:
299 if not format_dict:
283 # nothing to display (e.g. _ipython_display_ took over)
300 # nothing to display (e.g. _ipython_display_ took over)
284 continue
301 continue
285 if metadata:
302 if metadata:
286 # kwarg-specified metadata gets precedence
303 # kwarg-specified metadata gets precedence
287 _merge(md_dict, metadata)
304 _merge(md_dict, metadata)
288 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
305 publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
289 if display_id:
306 if display_id:
290 return DisplayHandle(display_id)
307 return DisplayHandle(display_id)
291
308
292
309
293 # use * for keyword-only display_id arg
310 # use * for keyword-only display_id arg
294 def update_display(obj, *, display_id, **kwargs):
311 def update_display(obj, *, display_id, **kwargs):
295 """Update an existing display by id
312 """Update an existing display by id
296
313
297 Parameters
314 Parameters
298 ----------
315 ----------
299 obj
316 obj
300 The object with which to update the display
317 The object with which to update the display
301 display_id : keyword-only
318 display_id : keyword-only
302 The id of the display to update
319 The id of the display to update
303
320
304 See Also
321 See Also
305 --------
322 --------
306 :func:`display`
323 :func:`display`
307 """
324 """
308 kwargs['update'] = True
325 kwargs['update'] = True
309 display(obj, display_id=display_id, **kwargs)
326 display(obj, display_id=display_id, **kwargs)
310
327
311
328
312 class DisplayHandle(object):
329 class DisplayHandle(object):
313 """A handle on an updatable display
330 """A handle on an updatable display
314
331
315 Call `.update(obj)` to display a new object.
332 Call `.update(obj)` to display a new object.
316
333
317 Call `.display(obj`) to add a new instance of this display,
334 Call `.display(obj`) to add a new instance of this display,
318 and update existing instances.
335 and update existing instances.
319
336
320 See Also
337 See Also
321 --------
338 --------
322
339
323 :func:`display`, :func:`update_display`
340 :func:`display`, :func:`update_display`
324
341
325 """
342 """
326
343
327 def __init__(self, display_id=None):
344 def __init__(self, display_id=None):
328 if display_id is None:
345 if display_id is None:
329 display_id = _new_id()
346 display_id = _new_id()
330 self.display_id = display_id
347 self.display_id = display_id
331
348
332 def __repr__(self):
349 def __repr__(self):
333 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
350 return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
334
351
335 def display(self, obj, **kwargs):
352 def display(self, obj, **kwargs):
336 """Make a new display with my id, updating existing instances.
353 """Make a new display with my id, updating existing instances.
337
354
338 Parameters
355 Parameters
339 ----------
356 ----------
340 obj
357 obj
341 object to display
358 object to display
342 **kwargs
359 **kwargs
343 additional keyword arguments passed to display
360 additional keyword arguments passed to display
344 """
361 """
345 display(obj, display_id=self.display_id, **kwargs)
362 display(obj, display_id=self.display_id, **kwargs)
346
363
347 def update(self, obj, **kwargs):
364 def update(self, obj, **kwargs):
348 """Update existing displays with my id
365 """Update existing displays with my id
349
366
350 Parameters
367 Parameters
351 ----------
368 ----------
352 obj
369 obj
353 object to display
370 object to display
354 **kwargs
371 **kwargs
355 additional keyword arguments passed to update_display
372 additional keyword arguments passed to update_display
356 """
373 """
357 update_display(obj, display_id=self.display_id, **kwargs)
374 update_display(obj, display_id=self.display_id, **kwargs)
358
375
359
376
360 def clear_output(wait=False):
377 def clear_output(wait=False):
361 """Clear the output of the current cell receiving output.
378 """Clear the output of the current cell receiving output.
362
379
363 Parameters
380 Parameters
364 ----------
381 ----------
365 wait : bool [default: false]
382 wait : bool [default: false]
366 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."""
367 from IPython.core.interactiveshell import InteractiveShell
384 from IPython.core.interactiveshell import InteractiveShell
368 if InteractiveShell.initialized():
385 if InteractiveShell.initialized():
369 InteractiveShell.instance().display_pub.clear_output(wait)
386 InteractiveShell.instance().display_pub.clear_output(wait)
370 else:
387 else:
371 print('\033[2K\r', end='')
388 print('\033[2K\r', end='')
372 sys.stdout.flush()
389 sys.stdout.flush()
373 print('\033[2K\r', end='')
390 print('\033[2K\r', end='')
374 sys.stderr.flush()
391 sys.stderr.flush()
@@ -1,239 +1,242 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the TerminalInteractiveShell and related pieces."""
2 """Tests for the TerminalInteractiveShell and related pieces."""
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import sys
6 import sys
7 import unittest
7 import unittest
8 import os
8 import os
9
9
10 from IPython.core.inputtransformer import InputTransformer
10 from IPython.core.inputtransformer import InputTransformer
11 from IPython.testing import tools as tt
11 from IPython.testing import tools as tt
12 from IPython.utils.capture import capture_output
12 from IPython.utils.capture import capture_output
13
13
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
15
15
16
16
17 class TestElide(unittest.TestCase):
17 class TestElide(unittest.TestCase):
18 def test_elide(self):
18 def test_elide(self):
19 _elide("concatenate((a1, a2, ...), axis", "") # do not raise
19 _elide("concatenate((a1, a2, ...), axis", "") # do not raise
20 _elide("concatenate((a1, a2, ..), . axis", "") # do not raise
20 _elide("concatenate((a1, a2, ..), . axis", "") # do not raise
21 self.assertEqual(
21 self.assertEqual(
22 _elide("aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh", ""),
22 _elide("aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh", ""),
23 "aaaa.b…g.hhhhhh",
23 "aaaa.b…g.hhhhhh",
24 )
24 )
25
25
26 test_string = os.sep.join(["", 10 * "a", 10 * "b", 10 * "c", ""])
26 test_string = os.sep.join(["", 10 * "a", 10 * "b", 10 * "c", ""])
27 expect_stirng = (
27 expect_stirng = (
28 os.sep + "a" + "\N{HORIZONTAL ELLIPSIS}" + "b" + os.sep + 10 * "c"
28 os.sep + "a" + "\N{HORIZONTAL ELLIPSIS}" + "b" + os.sep + 10 * "c"
29 )
29 )
30 self.assertEqual(_elide(test_string, ""), expect_stirng)
30 self.assertEqual(_elide(test_string, ""), expect_stirng)
31
31
32 def test_elide_typed_normal(self):
32 def test_elide_typed_normal(self):
33 self.assertEqual(
33 self.assertEqual(
34 _elide(
34 _elide(
35 "the quick brown fox jumped over the lazy dog",
35 "the quick brown fox jumped over the lazy dog",
36 "the quick brown fox",
36 "the quick brown fox",
37 min_elide=10,
37 min_elide=10,
38 ),
38 ),
39 "the…fox jumped over the lazy dog",
39 "the…fox jumped over the lazy dog",
40 )
40 )
41
41
42 def test_elide_typed_short_match(self):
42 def test_elide_typed_short_match(self):
43 """
43 """
44 if the match is too short we don't elide.
44 if the match is too short we don't elide.
45 avoid the "the...the"
45 avoid the "the...the"
46 """
46 """
47 self.assertEqual(
47 self.assertEqual(
48 _elide("the quick brown fox jumped over the lazy dog", "the", min_elide=10),
48 _elide("the quick brown fox jumped over the lazy dog", "the", min_elide=10),
49 "the quick brown fox jumped over the lazy dog",
49 "the quick brown fox jumped over the lazy dog",
50 )
50 )
51
51
52 def test_elide_typed_no_match(self):
52 def test_elide_typed_no_match(self):
53 """
53 """
54 if the match is too short we don't elide.
54 if the match is too short we don't elide.
55 avoid the "the...the"
55 avoid the "the...the"
56 """
56 """
57 # here we typed red instead of brown
57 # here we typed red instead of brown
58 self.assertEqual(
58 self.assertEqual(
59 _elide(
59 _elide(
60 "the quick brown fox jumped over the lazy dog",
60 "the quick brown fox jumped over the lazy dog",
61 "the quick red fox",
61 "the quick red fox",
62 min_elide=10,
62 min_elide=10,
63 ),
63 ),
64 "the quick brown fox jumped over the lazy dog",
64 "the quick brown fox jumped over the lazy dog",
65 )
65 )
66
66
67
67
68 class TestContextAwareCompletion(unittest.TestCase):
68 class TestContextAwareCompletion(unittest.TestCase):
69 def test_adjust_completion_text_based_on_context(self):
69 def test_adjust_completion_text_based_on_context(self):
70 # Adjusted case
70 # Adjusted case
71 self.assertEqual(
71 self.assertEqual(
72 _adjust_completion_text_based_on_context("arg1=", "func1(a=)", 7), "arg1"
72 _adjust_completion_text_based_on_context("arg1=", "func1(a=)", 7), "arg1"
73 )
73 )
74
74
75 # Untouched cases
75 # Untouched cases
76 self.assertEqual(
76 self.assertEqual(
77 _adjust_completion_text_based_on_context("arg1=", "func1(a)", 7), "arg1="
77 _adjust_completion_text_based_on_context("arg1=", "func1(a)", 7), "arg1="
78 )
78 )
79 self.assertEqual(
79 self.assertEqual(
80 _adjust_completion_text_based_on_context("arg1=", "func1(a", 7), "arg1="
80 _adjust_completion_text_based_on_context("arg1=", "func1(a", 7), "arg1="
81 )
81 )
82 self.assertEqual(
82 self.assertEqual(
83 _adjust_completion_text_based_on_context("%magic", "func1(a=)", 7), "%magic"
83 _adjust_completion_text_based_on_context("%magic", "func1(a=)", 7), "%magic"
84 )
84 )
85 self.assertEqual(
85 self.assertEqual(
86 _adjust_completion_text_based_on_context("func2", "func1(a=)", 7), "func2"
86 _adjust_completion_text_based_on_context("func2", "func1(a=)", 7), "func2"
87 )
87 )
88
88
89
89
90 # Decorator for interaction loop tests -----------------------------------------
90 # Decorator for interaction loop tests -----------------------------------------
91
91
92
92
93 class mock_input_helper(object):
93 class mock_input_helper(object):
94 """Machinery for tests of the main interact loop.
94 """Machinery for tests of the main interact loop.
95
95
96 Used by the mock_input decorator.
96 Used by the mock_input decorator.
97 """
97 """
98 def __init__(self, testgen):
98 def __init__(self, testgen):
99 self.testgen = testgen
99 self.testgen = testgen
100 self.exception = None
100 self.exception = None
101 self.ip = get_ipython()
101 self.ip = get_ipython()
102
102
103 def __enter__(self):
103 def __enter__(self):
104 self.orig_prompt_for_code = self.ip.prompt_for_code
104 self.orig_prompt_for_code = self.ip.prompt_for_code
105 self.ip.prompt_for_code = self.fake_input
105 self.ip.prompt_for_code = self.fake_input
106 return self
106 return self
107
107
108 def __exit__(self, etype, value, tb):
108 def __exit__(self, etype, value, tb):
109 self.ip.prompt_for_code = self.orig_prompt_for_code
109 self.ip.prompt_for_code = self.orig_prompt_for_code
110
110
111 def fake_input(self):
111 def fake_input(self):
112 try:
112 try:
113 return next(self.testgen)
113 return next(self.testgen)
114 except StopIteration:
114 except StopIteration:
115 self.ip.keep_running = False
115 self.ip.keep_running = False
116 return u''
116 return u''
117 except:
117 except:
118 self.exception = sys.exc_info()
118 self.exception = sys.exc_info()
119 self.ip.keep_running = False
119 self.ip.keep_running = False
120 return u''
120 return u''
121
121
122 def mock_input(testfunc):
122 def mock_input(testfunc):
123 """Decorator for tests of the main interact loop.
123 """Decorator for tests of the main interact loop.
124
124
125 Write the test as a generator, yield-ing the input strings, which IPython
125 Write the test as a generator, yield-ing the input strings, which IPython
126 will see as if they were typed in at the prompt.
126 will see as if they were typed in at the prompt.
127 """
127 """
128 def test_method(self):
128 def test_method(self):
129 testgen = testfunc(self)
129 testgen = testfunc(self)
130 with mock_input_helper(testgen) as mih:
130 with mock_input_helper(testgen) as mih:
131 mih.ip.interact()
131 mih.ip.interact()
132
132
133 if mih.exception is not None:
133 if mih.exception is not None:
134 # Re-raise captured exception
134 # Re-raise captured exception
135 etype, value, tb = mih.exception
135 etype, value, tb = mih.exception
136 import traceback
136 import traceback
137 traceback.print_tb(tb, file=sys.stdout)
137 traceback.print_tb(tb, file=sys.stdout)
138 del tb # Avoid reference loop
138 del tb # Avoid reference loop
139 raise value
139 raise value
140
140
141 return test_method
141 return test_method
142
142
143 # Test classes -----------------------------------------------------------------
143 # Test classes -----------------------------------------------------------------
144
144
145 class InteractiveShellTestCase(unittest.TestCase):
145 class InteractiveShellTestCase(unittest.TestCase):
146 def rl_hist_entries(self, rl, n):
146 def rl_hist_entries(self, rl, n):
147 """Get last n readline history entries as a list"""
147 """Get last n readline history entries as a list"""
148 return [rl.get_history_item(rl.get_current_history_length() - x)
148 return [rl.get_history_item(rl.get_current_history_length() - x)
149 for x in range(n - 1, -1, -1)]
149 for x in range(n - 1, -1, -1)]
150
150
151 @mock_input
151 @mock_input
152 def test_inputtransformer_syntaxerror(self):
152 def test_inputtransformer_syntaxerror(self):
153 ip = get_ipython()
153 ip = get_ipython()
154 ip.input_transformers_post.append(syntax_error_transformer)
154 ip.input_transformers_post.append(syntax_error_transformer)
155
155
156 try:
156 try:
157 #raise Exception
157 #raise Exception
158 with tt.AssertPrints('4', suppress=False):
158 with tt.AssertPrints('4', suppress=False):
159 yield u'print(2*2)'
159 yield u'print(2*2)'
160
160
161 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
161 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
162 yield u'print(2345) # syntaxerror'
162 yield u'print(2345) # syntaxerror'
163
163
164 with tt.AssertPrints('16', suppress=False):
164 with tt.AssertPrints('16', suppress=False):
165 yield u'print(4*4)'
165 yield u'print(4*4)'
166
166
167 finally:
167 finally:
168 ip.input_transformers_post.remove(syntax_error_transformer)
168 ip.input_transformers_post.remove(syntax_error_transformer)
169
169
170 def test_repl_not_plain_text(self):
170 def test_repl_not_plain_text(self):
171 ip = get_ipython()
171 ip = get_ipython()
172 formatter = ip.display_formatter
172 formatter = ip.display_formatter
173 assert formatter.active_types == ['text/plain']
173 assert formatter.active_types == ['text/plain']
174
174
175 # terminal may have arbitrary mimetype handler to open external viewer
175 # terminal may have arbitrary mimetype handler to open external viewer
176 # or inline images.
176 # or inline images.
177 assert formatter.ipython_display_formatter.enabled
177 assert formatter.ipython_display_formatter.enabled
178
178
179 class Test(object):
179 class Test(object):
180 def __repr__(self):
180 def __repr__(self):
181 return "<Test %i>" % id(self)
181 return "<Test %i>" % id(self)
182
182
183 def _repr_html_(self):
183 def _repr_html_(self):
184 return '<html>'
184 return '<html>'
185
185
186 # verify that HTML repr isn't computed
186 # verify that HTML repr isn't computed
187 obj = Test()
187 obj = Test()
188 data, _ = formatter.format(obj)
188 data, _ = formatter.format(obj)
189 self.assertEqual(data, {'text/plain': repr(obj)})
189 self.assertEqual(data, {'text/plain': repr(obj)})
190
190
191 class Test2(Test):
191 class Test2(Test):
192 def _ipython_display_(self):
192 def _ipython_display_(self):
193 from IPython.display import display, HTML
193 from IPython.display import display, HTML
194 display(HTML('<custom>'))
194
195 display(HTML("<custom>"))
195
196
196 # verify that mimehandlers are called
197 # verify that mimehandlers are called
197 called = False
198 called = False
198
199
199 def handler(data, metadata):
200 def handler(data, metadata):
200 print('Handler called')
201 print("Handler called")
201 nonlocal called
202 nonlocal called
202 called = True
203 called = True
203
204
204 ip.display_formatter.active_types.append("text/html")
205 ip.display_formatter.active_types.append("text/html")
205 ip.display_formatter.formatters["text/html"].enabled = True
206 ip.display_formatter.formatters["text/html"].enabled = True
206 ip.mime_renderers["text/html"] = handler
207 ip.mime_renderers["text/html"] = handler
207
208 try:
208
209 obj = Test()
209 obj = Test()
210 display(obj)
210 display(obj)
211 finally:
212 ip.display_formatter.formatters["text/html"].enabled = False
213 del ip.mime_renderers["text/html"]
211
214
212 assert called == True
215 assert called == True
213
216
214
217
215 def syntax_error_transformer(lines):
218 def syntax_error_transformer(lines):
216 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
219 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
217 for line in lines:
220 for line in lines:
218 pos = line.find('syntaxerror')
221 pos = line.find('syntaxerror')
219 if pos >= 0:
222 if pos >= 0:
220 e = SyntaxError('input contains "syntaxerror"')
223 e = SyntaxError('input contains "syntaxerror"')
221 e.text = line
224 e.text = line
222 e.offset = pos + 1
225 e.offset = pos + 1
223 raise e
226 raise e
224 return lines
227 return lines
225
228
226
229
227 class TerminalMagicsTestCase(unittest.TestCase):
230 class TerminalMagicsTestCase(unittest.TestCase):
228 def test_paste_magics_blankline(self):
231 def test_paste_magics_blankline(self):
229 """Test that code with a blank line doesn't get split (gh-3246)."""
232 """Test that code with a blank line doesn't get split (gh-3246)."""
230 ip = get_ipython()
233 ip = get_ipython()
231 s = ('def pasted_func(a):\n'
234 s = ('def pasted_func(a):\n'
232 ' b = a+1\n'
235 ' b = a+1\n'
233 '\n'
236 '\n'
234 ' return b')
237 ' return b')
235
238
236 tm = ip.magics_manager.registry['TerminalMagics']
239 tm = ip.magics_manager.registry['TerminalMagics']
237 tm.store_or_execute(s, name=None)
240 tm.store_or_execute(s, name=None)
238
241
239 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
242 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
General Comments 0
You need to be logged in to leave comments. Login now