Skip to content
This repository

Generate "All magics..." menu live #956

Merged
merged 5 commits into from over 2 years ago

2 participants

Matthias Bussonnier Fernando Perez
Matthias Bussonnier
Collaborator

Hi everyone,

I'm trying to catch up with the merging of the qtconsole. For now constructing the "all magic menu" by querying the kernel about get_ipython().lsmagic() through user_expressions. I thought it might be a good idea.

It does work as long as the action is not automatically done at startup, otherwise there is no prompt.
( try adding :self.window.pop.trigger() just before self.app._exec() in qtconsole.py )
You can trigger the action by hand through the Magics > Populate All Magic Menu Menu, which create a second All Magics Menu.

I don't know why, it's not working at startup, and i'm not sure adding a timer to execute this action later is a good idea.
I was also thinking of updating at each new prompt, or every time the user click on Magic Menu.

If can get a little help on this ...

Otherwise, still not 100% back, still digging in my backups to recover some data, but will do my best.
--
Matthias

Matthias Bussonnier
Collaborator

Hi,

Found what I was looking for. I've boud the action to update the menu to a first_response signal.
The menu is now populated with all the magics from %lsmagic.

I've also append '?' to the end of some magic which failed when invoked alone.

Matthias Bussonnier
Collaborator

rebased on master, and forced push..

Fernando Perez
Owner

Glad to have you back @Carreau. I'll try to review this over the weekend, if nobody beats me to it.

Fernando Perez
Owner

I found a bit of time to do it now :)

I like this and I think it's useful, as it will encourage users to browse around and try some magics they might not know about. It also seems to work perfectly on my system. I'll add some inline comments that are fairly minor code tidying issues and should be pretty easy to address.

IPython/frontend/qt/console/frontend_widget.py
... ...
@@ -137,6 +138,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
137 138
         self._input_splitter = self._input_splitter_class(input_mode='cell')
138 139
         self._kernel_manager = None
139 140
         self._request_info = {}
  141
+        self._callback_dict=dict([])
1
Fernando Perez Owner
fperez added a note

Create the emtpy dict with {} like the line above, for consistency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
... ...
@@ -311,6 +313,42 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
311 313
             cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 314
             self._complete_with_items(cursor, rep['content']['matches'])
313 315
 
  316
+    def _silent_exec_callback(self,expr,callback):
1
Fernando Perez Owner
fperez added a note

pep-8: use a space after each comma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
... ...
@@ -311,6 +313,42 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
311 313
             cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 314
             self._complete_with_items(cursor, rep['content']['matches'])
313 315
 
  316
+    def _silent_exec_callback(self,expr,callback):
  317
+        """ silently execute a function in the kernel and send the reply to the callback
1
Fernando Perez Owner
fperez added a note

Docstring formatting: Capitalize properly all sentences, no extra spaces at the beginning of each sentence, and use the formatting guidelines for docstring parameters. We follow the numpy docstring standard, as documented in our developer guide.

Once ytou get used to following proper formatting conventions, they become second nature. And since we follow the same standard as numpy, scipy and many other projects in the 'scipy ecosystem', it will be a good habit to pick up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez fperez commented on the diff
IPython/frontend/qt/console/frontend_widget.py
... ...
@@ -311,6 +313,42 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
311 313
             cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 314
             self._complete_with_items(cursor, rep['content']['matches'])
313 315
 
  316
+    def _silent_exec_callback(self,expr,callback):
  317
+        """ silently execute a function in the kernel and send the reply to the callback
  318
+
  319
+            expr should be a valid string to be executed by the kernel.
  320
+
  321
+            callback a function accepting one argument (str)
  322
+
  323
+            the callback is called with the 'repr()' of the result as first argument.
  324
+            to get the object, do 'eval()' on the passed value.
  325
+        """
  326
+
  327
+        # generate uuid, which would be used as a indication of wether or not
  328
+        # the unique request originate from here (can use msg id ?)
  329
+        local_uuid = str(uuid.uuid1())
  330
+        msg_id = self.kernel_manager.shell_channel.execute('',
1
Fernando Perez Owner
fperez added a note

No need to break up this call into so many extra lines. You should keep individual lines to less than 80 characters, but otherwise you can finish up the dict and closing parens all in one line, for example:

msg_id = self.kernel_manager.shell_channel.execute('', 
            silent=True, user_expressions={ local_uuid:expr } )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((8 lines not shown))
  320
+
  321
+            callback a function accepting one argument (str)
  322
+
  323
+            the callback is called with the 'repr()' of the result as first argument.
  324
+            to get the object, do 'eval()' on the passed value.
  325
+        """
  326
+
  327
+        # generate uuid, which would be used as a indication of wether or not
  328
+        # the unique request originate from here (can use msg id ?)
  329
+        local_uuid = str(uuid.uuid1())
  330
+        msg_id = self.kernel_manager.shell_channel.execute('',
  331
+            silent=True,
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
1
Fernando Perez Owner
fperez added a note

pep-8: spaces around = sign missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((11 lines not shown))
  323
+            the callback is called with the 'repr()' of the result as first argument.
  324
+            to get the object, do 'eval()' on the passed value.
  325
+        """
  326
+
  327
+        # generate uuid, which would be used as a indication of wether or not
  328
+        # the unique request originate from here (can use msg id ?)
  329
+        local_uuid = str(uuid.uuid1())
  330
+        msg_id = self.kernel_manager.shell_channel.execute('',
  331
+            silent=True,
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
1
Fernando Perez Owner
fperez added a note

pep8: space after comma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((12 lines not shown))
  324
+            to get the object, do 'eval()' on the passed value.
  325
+        """
  326
+
  327
+        # generate uuid, which would be used as a indication of wether or not
  328
+        # the unique request originate from here (can use msg id ?)
  329
+        local_uuid = str(uuid.uuid1())
  330
+        msg_id = self.kernel_manager.shell_channel.execute('',
  331
+            silent=True,
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
  339
+        """ Called when _silent_exec_callback message comme back from the kernel.
1
Fernando Perez Owner
fperez added a note

Docstring should follow standard, see above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((17 lines not shown))
  329
+        local_uuid = str(uuid.uuid1())
  330
+        msg_id = self.kernel_manager.shell_channel.execute('',
  331
+            silent=True,
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
  339
+        """ Called when _silent_exec_callback message comme back from the kernel.
  340
+
  341
+            Strip the message comming back from the kernel  and send it to the
  342
+            corresponding callback function.
  343
+        """
  344
+        cnt=msg['content']
1
Fernando Perez Owner
fperez added a note

spaces around = signs here and elsewhere, won't repeat it in every line but check the whole diff and fix everywhere (though read pep8, they are not to be added in keyword args).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((19 lines not shown))
  331
+            silent=True,
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
  339
+        """ Called when _silent_exec_callback message comme back from the kernel.
  340
+
  341
+            Strip the message comming back from the kernel  and send it to the
  342
+            corresponding callback function.
  343
+        """
  344
+        cnt=msg['content']
  345
+        ue=cnt['user_expressions']
  346
+        for i in ue.keys():
1
Fernando Perez Owner
fperez added a note

A dict can be iterated without calling .keys(), which is both clearer and faster, as it prevents the creation of an unnecessary list. Simply use for i in ue:.

Argh, never mind. I hadn't noticed you were modifying the dict, in that case you do need to create the static keys list. Ignore the above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((22 lines not shown))
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
  339
+        """ Called when _silent_exec_callback message comme back from the kernel.
  340
+
  341
+            Strip the message comming back from the kernel  and send it to the
  342
+            corresponding callback function.
  343
+        """
  344
+        cnt=msg['content']
  345
+        ue=cnt['user_expressions']
  346
+        for i in ue.keys():
  347
+            if i in self._callback_dict.keys():
  348
+                f= self._callback_dict[i]
  349
+                f(ue[i])
1
Fernando Perez Owner
fperez added a note

No need to create a separate f variable here just to call it right away, simply write self._callback_dict[i](ue[i])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((20 lines not shown))
  332
+            user_expressions={ local_uuid:expr,
  333
+                }
  334
+            )
  335
+        self._callback_dict[local_uuid]=callback
  336
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  337
+
  338
+    def _handle_exec_callback(self,msg):
  339
+        """ Called when _silent_exec_callback message comme back from the kernel.
  340
+
  341
+            Strip the message comming back from the kernel  and send it to the
  342
+            corresponding callback function.
  343
+        """
  344
+        cnt=msg['content']
  345
+        ue=cnt['user_expressions']
  346
+        for i in ue.keys():
  347
+            if i in self._callback_dict.keys():
1
Fernando Perez Owner
fperez added a note

Never test dict membership via the keys() list, which is an O(n) operation, use instead if i in self._callback_dict. It's shorter to type, clearer and O(1), so potentially much much faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/mainwindow.py
... ...
@@ -544,10 +544,44 @@ class MainWindow(QtGui.QMainWindow):
544 544
 
545 545
         self.kernel_menu.addSeparator()
546 546
 
  547
+    def update_all_magic_menu(self):
  548
+        # first define a callback which will get the list of all magic and put it in the menu.
1
Fernando Perez Owner
fperez added a note

Docstring for method missing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/mainwindow.py
... ...
@@ -544,10 +544,44 @@ class MainWindow(QtGui.QMainWindow):
544 544
 
545 545
         self.kernel_menu.addSeparator()
546 546
 
  547
+    def update_all_magic_menu(self):
  548
+        # first define a callback which will get the list of all magic and put it in the menu.
  549
+        def populate_all_magic_menu(val=None):
  550
+            alm_magic_menu = self.all_magic_menu
  551
+            alm_magic_menu.clear()
  552
+            def make_dynamic_magic(i):
  553
+                    def inner_dynamic_magic():
1
Fernando Perez Owner
fperez added a note

Ouch, you have a 4-level deep nested local function definition! That kind of code is very hard to read and analyze, because of the amount of nested scopes one must track. Can you try to simplify this a little bit to use a flatter code, hopefully with zero or at most one locally defined inner function?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/mainwindow.py
... ...
@@ -544,10 +544,44 @@ class MainWindow(QtGui.QMainWindow):
544 544
 
545 545
         self.kernel_menu.addSeparator()
546 546
 
  547
+    def update_all_magic_menu(self):
  548
+        # first define a callback which will get the list of all magic and put it in the menu.
  549
+        def populate_all_magic_menu(val=None):
  550
+            alm_magic_menu = self.all_magic_menu
  551
+            alm_magic_menu.clear()
  552
+            def make_dynamic_magic(i):
  553
+                    def inner_dynamic_magic():
  554
+                        self.active_frontend.execute(i)
  555
+                    inner_dynamic_magic.__name__ = "dynamics_magic_s"
  556
+                    return inner_dynamic_magic
  557
+
  558
+            # list of protected magic that don't like to be called without argument
  559
+            # append '?' to the end to print the docstring when called from the menu
  560
+            protected_magic= ["more","less","load_ext","pycat","loadpy","save"]
1
Fernando Perez Owner
fperez added a note

If you are going to be testing for membership in this set, you should make it a set() instead of a list with

protected_magic= set(["more","less","load_ext","pycat","loadpy","save"])

which will make the membership checks much faster.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez fperez commented on the diff
IPython/frontend/qt/console/mainwindow.py
((31 lines not shown))
547 574
     def init_magic_menu(self):
548 575
         self.magic_menu = self.menuBar().addMenu("&Magic")
549 576
         self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
550  
-        
  577
+
  578
+        # this action should not appear as it will be cleard when menu
  579
+        # will be updated at first kernel response.
  580
+        self.pop = QtGui.QAction("&Update All Magic Menu ",
  581
+            self,
1
Fernando Perez Owner
fperez added a note

this can fit into the line above, no need to break it up into so many.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/qtconsoleapp.py
... ...
@@ -451,6 +451,10 @@ class IPythonQtConsoleApp(BaseIPythonApplication):
451 451
         self.window.log = self.log
452 452
         self.window.add_tab_with_frontend(self.widget)
453 453
         self.window.init_menu_bar()
  454
+
  455
+        #we need to populate the 'Magic Menu' once the kernel has answer at least once
1
Fernando Perez Owner
fperez added a note

pep8: Leave a space after the # comment marker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez
Owner

OK, I've left a bunch of feedback. Most of it is easy to address and mostly code formatting/documentation issues that you'll get used to quickly. There's only one issue that probably will require some code refactoring.

But overall it's a nice improvement, thanks again!

Matthias Bussonnier
Collaborator

@fperez,
Just pushed a squashed commit of all the suggestion you made, I was a little lazy about code formating.
If you are ok with that I can push a squashed commit of everything rebased on the last master.

Fernando Perez
Owner

Sure, go ahead and force push a cleaned up commit if you prefer. I need to tune out of ipython for the day, I have some local work I must tend to now. But I'll get back to this as soon as I can, probably tomorrow.

Matthias Bussonnier
Collaborator

Squashed, rebased on master, and forced pushed ...

Matthias Bussonnier
Collaborator

Rebase on master agin to stay up to date....

IPython/frontend/qt/console/mainwindow.py
((23 lines not shown))
  566
+    def populate_all_magic_menu(self, listofmagic=None):
  567
+        """Clean "All Magics..." menu and repopulate it with `listofmagic`
  568
+
  569
+        `listofmagic` : string, repr() of a list of strings.
  570
+
  571
+        `listofmagic`is a repr() of list because it is fed with the result of
  572
+        a 'user_expression'
  573
+        """
  574
+        alm_magic_menu = self.all_magic_menu
  575
+        alm_magic_menu.clear()
  576
+
  577
+        # list of protected magic that don't like to be called without argument
  578
+        # append '?' to the end to print the docstring when called from the menu
  579
+        protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
  580
+
  581
+        for magic in eval(listofmagic):
2
Fernando Perez Owner
fperez added a note

Why are you using eval here? Eval is a fairly dangerous construct that should only be necessary in very specialized places, I don't see why it's needed here...

Matthias Bussonnier Collaborator
Carreau added a note

you'r right, it can be done by regExp ... that was because the return value by the kernel was the repr() of the list, so I thought to use eval without trying harder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/mainwindow.py
((36 lines not shown))
  579
+        protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
  580
+
  581
+        for magic in eval(listofmagic):
  582
+            if magic in protected_magic:
  583
+                pmagic = '%s%s%s'%('%',magic,'?')
  584
+            else:
  585
+                pmagic = '%s%s'%('%',magic)
  586
+            xaction = QtGui.QAction(pmagic,
  587
+                self,
  588
+                triggered=self._make_dynamic_magic(pmagic)
  589
+                )
  590
+            alm_magic_menu.addAction(xaction)
  591
+
  592
+    def update_all_magic_menu(self):
  593
+        # first define a callback which will get the list of all magic and put it in the menu.
  594
+        self.active_frontend._silent_exec_callback('get_ipython().lsmagic()',self.populate_all_magic_menu)
2
Fernando Perez Owner
fperez added a note

pep8, space after comma

Matthias Bussonnier Collaborator
Carreau added a note

missed that one...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
... ...
@@ -311,6 +313,45 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
311 313
             cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 314
             self._complete_with_items(cursor, rep['content']['matches'])
313 315
 
  316
+    def _silent_exec_callback(self, expr, callback):
  317
+        """Silently execute `expr` in the kernel and call `callback` with reply
  318
+
  319
+        `expr` : valid string to be executed by the kernel.
1
Fernando Perez Owner
fperez added a note

Thanks for updating the docstrings, but please make them conforming the numpy standard, these do not. Our development docs have an explanation. While this one in particular is a private method so it can be kept shorter, if you're going to document the arguments (which is great!), then it should be done in compliance with the rest of the codebase.

In short, they need to read

One line description.

more text
if necessary

Parameters
----------
  foo : str
    a string that is ...

  bar : int
    blah blah...

Returns
-------
x : blah...

Just have a look at the link above; once you get used to it they'll become second nature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((23 lines not shown))
  335
+        """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
  336
+
  337
+        `msg` : raw message send by the kernel containing an `user_expressions`
  338
+                and having a 'silent_exec_callback' kind.
  339
+
  340
+        This fonction will look for a `callback` associated with the
  341
+        corresponding message id. Association has been made by
  342
+        ``_silent_exec_callback``. `callback`is then called with the `repr()`
  343
+        of the value of corresponding `user_expressions` as argument.
  344
+        `callback` is then removed from the known list so that any message
  345
+        coming again with the same id won't trigger it.
  346
+        """
  347
+
  348
+        cnt = msg['content']
  349
+        ue = cnt['user_expressions']
  350
+        for i in ue.keys():
1
Fernando Perez Owner
fperez added a note

You can just iterate on ue itself: for i in ue. The dict that's getting modified is _callback_dict, so no need to build an explicit keys list here. You only need the explicit keys list if you want to simultaneously iterate and modify in-place the same dict.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/frontend/qt/console/frontend_widget.py
((26 lines not shown))
  338
+                and having a 'silent_exec_callback' kind.
  339
+
  340
+        This fonction will look for a `callback` associated with the
  341
+        corresponding message id. Association has been made by
  342
+        ``_silent_exec_callback``. `callback`is then called with the `repr()`
  343
+        of the value of corresponding `user_expressions` as argument.
  344
+        `callback` is then removed from the known list so that any message
  345
+        coming again with the same id won't trigger it.
  346
+        """
  347
+
  348
+        cnt = msg['content']
  349
+        ue = cnt['user_expressions']
  350
+        for i in ue.keys():
  351
+            if i in self._callback_dict:
  352
+                self._callback_dict[i](ue[i])
  353
+                self._callback_dict.pop(i)
1
Fernando Perez Owner
fperez added a note

pop returns the popped value, so these two lines should be replaced with just

self._callback_dict.pop(i)(ue[i])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez fperez commented on the diff
IPython/frontend/qt/console/mainwindow.py
((9 lines not shown))
  552
+        return `fun`, function with no parameters
  553
+
  554
+        `fun` execute `magic` an active frontend at the moment it is triggerd,
  555
+        not the active frontend at the moment it has been created.
  556
+
  557
+        This function is mostly used to create the "All Magics..." Menu at run time.
  558
+        """
  559
+        # need to level nested function  to be sure to past magic
  560
+        # on active frontend **at run time**.
  561
+        def inner_dynamic_magic():
  562
+            self.active_frontend.execute(magic)
  563
+        inner_dynamic_magic.__name__ = "dynamics_magic_s"
  564
+        return inner_dynamic_magic
  565
+
  566
+    def populate_all_magic_menu(self, listofmagic=None):
  567
+        """Clean "All Magics..." menu and repopulate it with `listofmagic`
1
Fernando Perez Owner
fperez added a note

Same comments as above on docstring formatting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez
Owner

Thanks, I think we're getting close! Just a few more fixes and we should be good to merge.

added some commits
Matthias Bussonnier Generate "All magics..." menu live
	this use 'user_expression' request to ask for the value of
	'get_ipython().lsmagic()' and populate the menu with the result.

	As is, the action need to be triggerd by hand, once the console
	have appear. Otherwith there is no prompt

	The logic can certainly also be shortend by using msg.id
	instead of uuid

	We should also keep a list of magic that shouldn't be executed without
	argument (eg: %gui) and don't put them in the menu. or appending '?' at the
	end
5881216
Matthias Bussonnier triger menu update when kernel started
	bind the update action to shell_channel's first_response event.
	add a list of magic that don't like beeing called alone ( like
	%more , %pycat , ...Etc) and append '?' in the menu to the end
	to show docsctring when clicked
	This list should be completed in :
		IPython/frontend/qt/console/mainwindow.py
e9402d7
Matthias Bussonnier protect two more magic in menu ("loadpy" and "save") 81e2cde
Matthias Bussonnier fix @fperez suggestions 414941c
Matthias Bussonnier fix docstrig, replace eval by regExp
	replace the `eval` to transform the `repr()` of the magic list send by the kernel
	by a regular expression, change the docstring to be numpy complient (I hope so)
81a8174
Matthias Bussonnier
Collaborator

Sooooory ... i fixed everything 2 or 3 days ago and forgot to push...
I rebased and foced push...

Fernando Perez
Owner

Great, very nice! Thanks for the excellent contribution, merging now. Everything looks good here.

Fernando Perez fperez merged commit 8fa6544 into from
Fernando Perez fperez closed this
Fernando Perez fperez referenced this pull request from a commit
Fernando Perez Temporary fix to work around #1057.
Basically it reverts the effect of #956 and goes back to a static list
for the 'all magics' menu.  I tried to mark very clearly the new code
so we can disable it once a proper fix for #1057 is committed, but we
can't have a broken qt console in master.
65546bf
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit in Carreau/ipython
Matthias Bussonnier Revert "Temporary fix to work around #1057."
    This reverts commit 65546bf, done to
    temporaly fixed a race condition introduced by #956, next commits should
    fixe this race condition
4573b16
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Matthias Bussonnier Carreau referenced this pull request from a commit in Carreau/ipython
Matthias Bussonnier qtconsole: fix race-cond, handle multiple exec
	fix race condition in qtconsole due to a race condition beween the population
	of the magic menu and the first prompt request. Resolved by upgrading the
	logic of the console to handle several executions request in parallel.

	Some namedTuple might still carry redundent information but the fix is the
	first priority

	fixes #1057 , introduced by #956
8553345
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Brian E. Granger ellisonbg referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Brian E. Granger ellisonbg referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Fernando Perez fperez referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 5 unique commits by 1 author.

Nov 24, 2011
Matthias Bussonnier Generate "All magics..." menu live
	this use 'user_expression' request to ask for the value of
	'get_ipython().lsmagic()' and populate the menu with the result.

	As is, the action need to be triggerd by hand, once the console
	have appear. Otherwith there is no prompt

	The logic can certainly also be shortend by using msg.id
	instead of uuid

	We should also keep a list of magic that shouldn't be executed without
	argument (eg: %gui) and don't put them in the menu. or appending '?' at the
	end
5881216
Matthias Bussonnier triger menu update when kernel started
	bind the update action to shell_channel's first_response event.
	add a list of magic that don't like beeing called alone ( like
	%more , %pycat , ...Etc) and append '?' in the menu to the end
	to show docsctring when clicked
	This list should be completed in :
		IPython/frontend/qt/console/mainwindow.py
e9402d7
Matthias Bussonnier protect two more magic in menu ("loadpy" and "save") 81e2cde
Matthias Bussonnier fix @fperez suggestions 414941c
Matthias Bussonnier fix docstrig, replace eval by regExp
	replace the `eval` to transform the `repr()` of the magic list send by the kernel
	by a regular expression, change the docstring to be numpy complient (I hope so)
81a8174
This page is out of date. Refresh to see the latest.
61  IPython/frontend/qt/console/frontend_widget.py
@@ -4,6 +4,7 @@
4 4
 from collections import namedtuple
5 5
 import sys
6 6
 import time
  7
+import uuid
7 8
 
8 9
 # System library imports
9 10
 from pygments.lexers import PythonLexer
@@ -137,6 +138,7 @@ def __init__(self, *args, **kw):
137 138
         self._input_splitter = self._input_splitter_class(input_mode='cell')
138 139
         self._kernel_manager = None
139 140
         self._request_info = {}
  141
+        self._callback_dict = {}
140 142
 
141 143
         # Configure the ConsoleWidget.
142 144
         self.tab_width = 4
@@ -311,6 +313,62 @@ def _handle_complete_reply(self, rep):
311 313
             cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 314
             self._complete_with_items(cursor, rep['content']['matches'])
313 315
 
  316
+    def _silent_exec_callback(self, expr, callback):
  317
+        """Silently execute `expr` in the kernel and call `callback` with reply
  318
+
  319
+        the `expr` is evaluated silently in the kernel (without) output in
  320
+        the frontend. Call `callback` with the
  321
+        `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
  322
+
  323
+        Parameters
  324
+        ----------
  325
+        expr : string
  326
+            valid string to be executed by the kernel.
  327
+        callback : function
  328
+            function accepting one arguement, as a string. The string will be
  329
+            the `repr` of the result of evaluating `expr`
  330
+
  331
+        The `callback` is called with the 'repr()' of the result of `expr` as
  332
+        first argument. To get the object, do 'eval()' onthe passed value.
  333
+
  334
+        See Also
  335
+        --------
  336
+        _handle_exec_callback : private method, deal with calling callback with reply
  337
+
  338
+        """
  339
+
  340
+        # generate uuid, which would be used as a indication of wether or not
  341
+        # the unique request originate from here (can use msg id ?)
  342
+        local_uuid = str(uuid.uuid1())
  343
+        msg_id = self.kernel_manager.shell_channel.execute('',
  344
+            silent=True, user_expressions={ local_uuid:expr })
  345
+        self._callback_dict[local_uuid] = callback
  346
+        self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
  347
+
  348
+    def _handle_exec_callback(self, msg):
  349
+        """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
  350
+
  351
+        Parameters
  352
+        ----------
  353
+        msg : raw message send by the kernel containing an `user_expressions`
  354
+                and having a 'silent_exec_callback' kind.
  355
+
  356
+        Notes
  357
+        -----
  358
+        This fonction will look for a `callback` associated with the
  359
+        corresponding message id. Association has been made by
  360
+        `_silent_exec_callback`. `callback` is then called with the `repr()`
  361
+        of the value of corresponding `user_expressions` as argument.
  362
+        `callback` is then removed from the known list so that any message
  363
+        coming again with the same id won't trigger it.
  364
+
  365
+        """
  366
+
  367
+        user_exp = msg['content']['user_expressions']
  368
+        for expression in user_exp:
  369
+            if expression in self._callback_dict:
  370
+                self._callback_dict.pop(expression)(user_exp[expression])
  371
+
314 372
     def _handle_execute_reply(self, msg):
315 373
         """ Handles replies for code execution.
316 374
         """
@@ -342,6 +400,9 @@ def _handle_execute_reply(self, msg):
342 400
 
343 401
             self._show_interpreter_prompt_for_reply(msg)
344 402
             self.executed.emit(msg)
  403
+        elif info and info.id == msg['parent_header']['msg_id'] and \
  404
+                info.kind == 'silent_exec_callback' and not self._hidden:
  405
+            self._handle_exec_callback(msg)
345 406
         else:
346 407
             super(FrontendWidget, self)._handle_execute_reply(msg)
347 408
 
111  IPython/frontend/qt/console/mainwindow.py
@@ -20,6 +20,7 @@
20 20
 
21 21
 # stdlib imports
22 22
 import sys
  23
+import re
23 24
 import webbrowser
24 25
 from threading import Thread
25 26
 
@@ -544,10 +545,89 @@ def init_kernel_menu(self):
544 545
 
545 546
         self.kernel_menu.addSeparator()
546 547
 
  548
+    def _make_dynamic_magic(self,magic):
  549
+        """Return a function `fun` that will execute `magic` on active frontend.
  550
+
  551
+        Parameters
  552
+        ----------
  553
+        magic : string
  554
+            string that will be executed as is when the returned function is called
  555
+
  556
+        Returns
  557
+        -------
  558
+        fun : function
  559
+            function with no parameters, when called will execute `magic` on the
  560
+            current active frontend at call time
  561
+
  562
+        See Also
  563
+        --------
  564
+        populate_all_magic_menu : generate the "All Magics..." menu
  565
+
  566
+        Notes
  567
+        -----
  568
+        `fun` execute `magic` an active frontend at the moment it is triggerd,
  569
+        not the active frontend at the moment it has been created.
  570
+
  571
+        This function is mostly used to create the "All Magics..." Menu at run time.
  572
+        """
  573
+        # need to level nested function  to be sure to past magic
  574
+        # on active frontend **at run time**.
  575
+        def inner_dynamic_magic():
  576
+            self.active_frontend.execute(magic)
  577
+        inner_dynamic_magic.__name__ = "dynamics_magic_s"
  578
+        return inner_dynamic_magic
  579
+
  580
+    def populate_all_magic_menu(self, listofmagic=None):
  581
+        """Clean "All Magics..." menu and repopulate it with `listofmagic`
  582
+
  583
+        Parameters
  584
+        ----------
  585
+        listofmagic : string,
  586
+            repr() of a list of strings, send back by the kernel
  587
+
  588
+        Notes
  589
+        -----
  590
+        `listofmagic`is a repr() of list because it is fed with the result of
  591
+        a 'user_expression'
  592
+        """
  593
+        alm_magic_menu = self.all_magic_menu
  594
+        alm_magic_menu.clear()
  595
+
  596
+        # list of protected magic that don't like to be called without argument
  597
+        # append '?' to the end to print the docstring when called from the menu
  598
+        protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
  599
+        magics=re.findall('\w+', listofmagic)
  600
+        for magic in magics:
  601
+            if magic in protected_magic:
  602
+                pmagic = '%s%s%s'%('%',magic,'?')
  603
+            else:
  604
+                pmagic = '%s%s'%('%',magic)
  605
+            xaction = QtGui.QAction(pmagic,
  606
+                self,
  607
+                triggered=self._make_dynamic_magic(pmagic)
  608
+                )
  609
+            alm_magic_menu.addAction(xaction)
  610
+
  611
+    def update_all_magic_menu(self):
  612
+        """ Update the list on magic in the "All Magics..." Menu
  613
+
  614
+        Request the kernel with the list of availlable magic and populate the
  615
+        menu with the list received back
  616
+
  617
+        """
  618
+        # first define a callback which will get the list of all magic and put it in the menu.
  619
+        self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
  620
+
547 621
     def init_magic_menu(self):
548 622
         self.magic_menu = self.menuBar().addMenu("&Magic")
549 623
         self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
550  
-        
  624
+
  625
+        # this action should not appear as it will be cleard when menu
  626
+        # will be updated at first kernel response.
  627
+        self.pop = QtGui.QAction("&Update All Magic Menu ",
  628
+            self, triggered=self.update_all_magic_menu)
  629
+        self.add_menu_action(self.all_magic_menu, self.pop)
  630
+
551 631
         self.reset_action = QtGui.QAction("&Reset",
552 632
             self,
553 633
             statusTip="Clear all varible from workspace",
@@ -583,34 +663,7 @@ def init_magic_menu(self):
583 663
             statusTip="List interactive variable with detail",
584 664
             triggered=self.whos_magic_active_frontend)
585 665
         self.add_menu_action(self.magic_menu, self.whos_action)
586  
-        
587  
-        # allmagics submenu:
588  
-        
589  
-        #for now this is just a copy and paste, but we should get this dynamically
590  
-        magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
591  
-            "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
592  
-            "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
593  
-            "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
594  
-            "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
595  
-            "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
596  
-            "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
597  
-            "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
598  
-            "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
599  
-            "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
600  
-
601  
-        def make_dynamic_magic(i):
602  
-                def inner_dynamic_magic():
603  
-                    self.active_frontend.execute(i)
604  
-                inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
605  
-                return inner_dynamic_magic
606  
-
607  
-        for magic in magiclist:
608  
-            xaction = QtGui.QAction(magic,
609  
-                self,
610  
-                triggered=make_dynamic_magic(magic)
611  
-                )
612  
-            self.all_magic_menu.addAction(xaction)
613  
-    
  666
+
614 667
     def init_window_menu(self):
615 668
         self.window_menu = self.menuBar().addMenu("&Window")
616 669
         if sys.platform == 'darwin':
4  IPython/frontend/qt/console/qtconsoleapp.py
@@ -451,6 +451,10 @@ def init_qt_elements(self):
451 451
         self.window.log = self.log
452 452
         self.window.add_tab_with_frontend(self.widget)
453 453
         self.window.init_menu_bar()
  454
+
  455
+        # we need to populate the 'Magic Menu' once the kernel has answer at least once
  456
+        self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger)
  457
+
454 458
         self.window.setWindowTitle('Python' if self.pure else 'IPython')
455 459
 
456 460
     def init_colors(self):
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.