##// END OF EJS Templates
Merge pull request #3319 from minrk/user-expressions...
Min RK -
r10828:2c614f32 merge
parent child Browse files
Show More
@@ -2363,9 +2363,37 b' class InteractiveShell(SingletonConfigurable):'
2363 2363 # Things related to extracting values/expressions from kernel and user_ns
2364 2364 #-------------------------------------------------------------------------
2365 2365
2366 def _simple_error(self):
2367 etype, value = sys.exc_info()[:2]
2368 return u'[ERROR] {e.__name__}: {v}'.format(e=etype, v=value)
2366 def _user_obj_error(self):
2367 """return simple exception dict
2368
2369 for use in user_variables / expressions
2370 """
2371
2372 etype, evalue, tb = self._get_exc_info()
2373 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2374
2375 exc_info = {
2376 u'status' : 'error',
2377 u'traceback' : stb,
2378 u'ename' : unicode(etype.__name__),
2379 u'evalue' : py3compat.safe_unicode(evalue),
2380 }
2381
2382 return exc_info
2383
2384 def _format_user_obj(self, obj):
2385 """format a user object to display dict
2386
2387 for use in user_expressions / variables
2388 """
2389
2390 data, md = self.display_formatter.format(obj)
2391 value = {
2392 'status' : 'ok',
2393 'data' : data,
2394 'metadata' : md,
2395 }
2396 return value
2369 2397
2370 2398 def user_variables(self, names):
2371 2399 """Get a list of variable names from the user's namespace.
@@ -2377,15 +2405,17 b' class InteractiveShell(SingletonConfigurable):'
2377 2405
2378 2406 Returns
2379 2407 -------
2380 A dict, keyed by the input names and with the repr() of each value.
2408 A dict, keyed by the input names and with the rich mime-type repr(s) of each value.
2409 Each element will be a sub-dict of the same form as a display_data message.
2381 2410 """
2382 2411 out = {}
2383 2412 user_ns = self.user_ns
2413
2384 2414 for varname in names:
2385 2415 try:
2386 value = repr(user_ns[varname])
2416 value = self._format_user_obj(user_ns[varname])
2387 2417 except:
2388 value = self._simple_error()
2418 value = self._user_obj_error()
2389 2419 out[varname] = value
2390 2420 return out
2391 2421
@@ -2401,17 +2431,18 b' class InteractiveShell(SingletonConfigurable):'
2401 2431
2402 2432 Returns
2403 2433 -------
2404 A dict, keyed like the input expressions dict, with the repr() of each
2405 value.
2434 A dict, keyed like the input expressions dict, with the rich mime-typed
2435 display_data of each value.
2406 2436 """
2407 2437 out = {}
2408 2438 user_ns = self.user_ns
2409 2439 global_ns = self.user_global_ns
2440
2410 2441 for key, expr in expressions.iteritems():
2411 2442 try:
2412 value = repr(eval(expr, global_ns, user_ns))
2443 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2413 2444 except:
2414 value = self._simple_error()
2445 value = self._user_obj_error()
2415 2446 out[key] = value
2416 2447 return out
2417 2448
@@ -15,6 +15,7 b''
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17 # Stdlib
18 import json
18 19 import os
19 20 import re
20 21 import sys
@@ -326,16 +327,6 b' class MagicsManager(Configurable):'
326 327 """Return descriptive string with automagic status."""
327 328 return self._auto_status[self.auto_magic]
328 329
329 def lsmagic_info(self):
330 magic_list = []
331 for m_type in self.magics :
332 for m_name,mgc in self.magics[m_type].items():
333 try :
334 magic_list.append({'name':m_name,'type':m_type,'class':mgc.im_class.__name__})
335 except AttributeError :
336 magic_list.append({'name':m_name,'type':m_type,'class':'Other'})
337 return magic_list
338
339 330 def lsmagic(self):
340 331 """Return a dict of currently available magic functions.
341 332
@@ -15,6 +15,7 b' from __future__ import print_function'
15 15
16 16 # Stdlib
17 17 import io
18 import json
18 19 import sys
19 20 from pprint import pformat
20 21
@@ -33,6 +34,55 b' from IPython.utils.warn import warn, error'
33 34 # Magics class implementation
34 35 #-----------------------------------------------------------------------------
35 36
37 class MagicsDisplay(object):
38 def __init__(self, magics_manager):
39 self.magics_manager = magics_manager
40
41 def _lsmagic(self):
42 """The main implementation of the %lsmagic"""
43 mesc = magic_escapes['line']
44 cesc = magic_escapes['cell']
45 mman = self.magics_manager
46 magics = mman.lsmagic()
47 out = ['Available line magics:',
48 mesc + (' '+mesc).join(sorted(magics['line'])),
49 '',
50 'Available cell magics:',
51 cesc + (' '+cesc).join(sorted(magics['cell'])),
52 '',
53 mman.auto_status()]
54 return '\n'.join(out)
55
56 def _repr_pretty_(self, p, cycle):
57 p.text(self._lsmagic())
58
59 def __str__(self):
60 return self._lsmagic()
61
62 def _jsonable(self):
63 """turn magics dict into jsonable dict of the same structure
64
65 replaces object instances with their class names as strings
66 """
67 magic_dict = {}
68 mman = self.magics_manager
69 magics = mman.lsmagic()
70 for key, subdict in magics.items():
71 d = {}
72 magic_dict[key] = d
73 for name, obj in subdict.items():
74 try:
75 classname = obj.im_class.__name__
76 except AttributeError:
77 classname = 'Other'
78
79 d[name] = classname
80 return magic_dict
81
82 def _repr_json_(self):
83 return json.dumps(self._jsonable())
84
85
36 86 @magics_class
37 87 class BasicMagics(Magics):
38 88 """Magics that provide central IPython functionality.
@@ -124,24 +174,10 b' class BasicMagics(Magics):'
124 174 magic_escapes['cell'], name,
125 175 magic_escapes['cell'], target))
126 176
127 def _lsmagic(self):
128 mesc = magic_escapes['line']
129 cesc = magic_escapes['cell']
130 mman = self.shell.magics_manager
131 magics = mman.lsmagic()
132 out = ['Available line magics:',
133 mesc + (' '+mesc).join(sorted(magics['line'])),
134 '',
135 'Available cell magics:',
136 cesc + (' '+cesc).join(sorted(magics['cell'])),
137 '',
138 mman.auto_status()]
139 return '\n'.join(out)
140
141 177 @line_magic
142 178 def lsmagic(self, parameter_s=''):
143 179 """List currently available magic functions."""
144 print(self._lsmagic())
180 return MagicsDisplay(self.shell.magics_manager)
145 181
146 182 def _magic_docs(self, brief=False, rest=False):
147 183 """Return docstrings from magic functions."""
@@ -578,3 +578,72 b' class TestAstTransformError(unittest.TestCase):'
578 578 def test__IPYTHON__():
579 579 # This shouldn't raise a NameError, that's all
580 580 __IPYTHON__
581
582
583 class DummyRepr(object):
584 def __repr__(self):
585 return "DummyRepr"
586
587 def _repr_html_(self):
588 return "<b>dummy</b>"
589
590 def _repr_javascript_(self):
591 return "console.log('hi');", {'key': 'value'}
592
593
594 def test_user_variables():
595 # enable all formatters
596 ip.display_formatter.active_types = ip.display_formatter.format_types
597
598 ip.user_ns['dummy'] = d = DummyRepr()
599 keys = set(['dummy', 'doesnotexist'])
600 r = ip.user_variables(keys)
601
602 nt.assert_equal(keys, set(r.keys()))
603 dummy = r['dummy']
604 nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys()))
605 nt.assert_equal(dummy['status'], 'ok')
606 data = dummy['data']
607 metadata = dummy['metadata']
608 nt.assert_equal(data.get('text/html'), d._repr_html_())
609 js, jsmd = d._repr_javascript_()
610 nt.assert_equal(data.get('application/javascript'), js)
611 nt.assert_equal(metadata.get('application/javascript'), jsmd)
612
613 dne = r['doesnotexist']
614 nt.assert_equal(dne['status'], 'error')
615 nt.assert_equal(dne['ename'], 'KeyError')
616
617 # back to text only
618 ip.display_formatter.active_types = ['text/plain']
619
620 def test_user_expression():
621 # enable all formatters
622 ip.display_formatter.active_types = ip.display_formatter.format_types
623 query = {
624 'a' : '1 + 2',
625 'b' : '1/0',
626 }
627 r = ip.user_expressions(query)
628 import pprint
629 pprint.pprint(r)
630 nt.assert_equal(r.keys(), query.keys())
631 a = r['a']
632 nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys()))
633 nt.assert_equal(a['status'], 'ok')
634 data = a['data']
635 metadata = a['metadata']
636 nt.assert_equal(data.get('text/plain'), '3')
637
638 b = r['b']
639 nt.assert_equal(b['status'], 'error')
640 nt.assert_equal(b['ename'], 'ZeroDivisionError')
641
642 # back to text only
643 ip.display_formatter.active_types = ['text/plain']
644
645
646
647
648
649
@@ -241,7 +241,9 b' class HistoryConsoleWidget(ConsoleWidget):'
241 241 content = msg['content']
242 242 status = content['status']
243 243 if status == 'ok':
244 self._max_session_history=(int(content['user_expressions']['hlen']))
244 self._max_session_history = int(
245 content['user_expressions']['hlen']['data']['text/plain']
246 )
245 247
246 248 def save_magic(self):
247 249 # update the session history length
@@ -20,15 +20,17 b' Authors:'
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 import sys
23 import json
24 24 import re
25 import sys
25 26 import webbrowser
26 import ast
27 27 from threading import Thread
28 28
29 29 # System library imports
30 30 from IPython.external.qt import QtGui,QtCore
31 31
32 from IPython.core.magic import magic_escapes
33
32 34 def background(f):
33 35 """call a function in a simple thread, to prevent blocking"""
34 36 t = Thread(target=f)
@@ -615,37 +617,36 b' class MainWindow(QtGui.QMainWindow):'
615 617 inner_dynamic_magic.__name__ = "dynamics_magic_s"
616 618 return inner_dynamic_magic
617 619
618 def populate_all_magic_menu(self, listofmagic=None):
619 """Clean "All Magics..." menu and repopulate it with `listofmagic`
620 def populate_all_magic_menu(self, display_data=None):
621 """Clean "All Magics..." menu and repopulate it with `display_data`
620 622
621 623 Parameters
622 624 ----------
623 listofmagic : string,
624 repr() of a list of strings, send back by the kernel
625 display_data : dict,
626 dict of display_data for the magics dict of a MagicsManager.
627 Expects json data, as the result of %lsmagic
625 628
626 Notes
627 -----
628 `listofmagic`is a repr() of list because it is fed with the result of
629 a 'user_expression'
630 629 """
631 630 for k,v in self._magic_menu_dict.items():
632 631 v.clear()
633 632 self.all_magic_menu.clear()
634 633
634 if not display_data:
635 return
635 636
636 mlist=ast.literal_eval(listofmagic)
637 for magic in mlist:
638 cell = (magic['type'] == 'cell')
639 name = magic['name']
640 mclass = magic['class']
641 if cell :
642 prefix='%%'
643 else :
644 prefix='%'
645 magic_menu = self._get_magic_menu(mclass)
637 if display_data['status'] != 'ok':
638 self.log.warn("%%lsmagic user-expression failed: %s" % display_data)
639 return
646 640
647 pmagic = '%s%s'%(prefix,name)
641 mdict = json.loads(display_data['data'].get('application/json', {}))
648 642
643 for mtype in sorted(mdict):
644 subdict = mdict[mtype]
645 prefix = magic_escapes[mtype]
646 for name in sorted(subdict):
647 mclass = subdict[name]
648 magic_menu = self._get_magic_menu(mclass)
649 pmagic = prefix + name
649 650 xaction = QtGui.QAction(pmagic,
650 651 self,
651 652 triggered=self._make_dynamic_magic(pmagic)
@@ -660,7 +661,7 b' class MainWindow(QtGui.QMainWindow):'
660 661 menu with the list received back
661 662
662 663 """
663 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
664 self.active_frontend._silent_exec_callback('get_ipython().magic("lsmagic")',
664 665 self.populate_all_magic_menu)
665 666
666 667 def _get_magic_menu(self,menuidentifier, menulabel=None):
@@ -361,7 +361,21 b' def test_user_variables():'
361 361
362 362 msg_id, reply = execute(code='x=1', user_variables=['x'])
363 363 user_variables = reply['user_variables']
364 nt.assert_equal(user_variables, {u'x' : u'1'})
364 nt.assert_equal(user_variables, {u'x': {
365 u'status': u'ok',
366 u'data': {u'text/plain': u'1'},
367 u'metadata': {},
368 }})
369
370
371 def test_user_variables_fail():
372 flush_channels()
373
374 msg_id, reply = execute(code='x=1', user_variables=['nosuchname'])
375 user_variables = reply['user_variables']
376 foo = user_variables['nosuchname']
377 nt.assert_equal(foo['status'], 'error')
378 nt.assert_equal(foo['ename'], 'KeyError')
365 379
366 380
367 381 def test_user_expressions():
@@ -369,7 +383,21 b' def test_user_expressions():'
369 383
370 384 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
371 385 user_expressions = reply['user_expressions']
372 nt.assert_equal(user_expressions, {u'foo' : u'2'})
386 nt.assert_equal(user_expressions, {u'foo': {
387 u'status': u'ok',
388 u'data': {u'text/plain': u'2'},
389 u'metadata': {},
390 }})
391
392
393 def test_user_expressions_fail():
394 flush_channels()
395
396 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
397 user_expressions = reply['user_expressions']
398 foo = user_expressions['foo']
399 nt.assert_equal(foo['status'], 'error')
400 nt.assert_equal(foo['ename'], 'NameError')
373 401
374 402
375 403 @dec.parametric
@@ -472,27 +472,6 b' class KernelMagics(Magics):'
472 472 else:
473 473 print("Autosave disabled")
474 474
475 def safe_unicode(e):
476 """unicode(e) with various fallbacks. Used for exceptions, which may not be
477 safe to call unicode() on.
478 """
479 try:
480 return unicode(e)
481 except UnicodeError:
482 pass
483
484 try:
485 return py3compat.str_to_unicode(str(e))
486 except UnicodeError:
487 pass
488
489 try:
490 return py3compat.str_to_unicode(repr(e))
491 except UnicodeError:
492 pass
493
494 return u'Unrecoverably corrupt evalue'
495
496 475
497 476 class ZMQInteractiveShell(InteractiveShell):
498 477 """A subclass of InteractiveShell for ZMQ."""
@@ -572,7 +551,7 b' class ZMQInteractiveShell(InteractiveShell):'
572 551 exc_content = {
573 552 u'traceback' : stb,
574 553 u'ename' : unicode(etype.__name__),
575 u'evalue' : safe_unicode(evalue)
554 u'evalue' : py3compat.safe_unicode(evalue),
576 555 }
577 556
578 557 dh = self.displayhook
@@ -50,6 +50,27 b' def _modify_str_or_docstring(str_change_func):'
50 50 return doc
51 51 return wrapper
52 52
53 def safe_unicode(e):
54 """unicode(e) with various fallbacks. Used for exceptions, which may not be
55 safe to call unicode() on.
56 """
57 try:
58 return unicode(e)
59 except UnicodeError:
60 pass
61
62 try:
63 return py3compat.str_to_unicode(str(e))
64 except UnicodeError:
65 pass
66
67 try:
68 return py3compat.str_to_unicode(repr(e))
69 except UnicodeError:
70 pass
71
72 return u'Unrecoverably corrupt evalue'
73
53 74 if sys.version_info[0] >= 3:
54 75 PY3 = True
55 76
@@ -171,8 +171,9 b' Message type: ``execute_request``::'
171 171 # is forced to be False.
172 172 'store_history' : bool,
173 173
174 # A list of variable names from the user's namespace to be retrieved. What
175 # returns is a JSON string of the variable's repr(), not a python object.
174 # A list of variable names from the user's namespace to be retrieved.
175 # What returns is a rich representation of each variable (dict keyed by name).
176 # See the display_data content for the structure of the representation data.
176 177 'user_variables' : list,
177 178
178 179 # Similarly, a dict mapping names to expressions to be evaluated in the
General Comments 0
You need to be logged in to leave comments. Login now