Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add BlockingKernelClient.execute_interactive #185

Merged
merged 9 commits into from
Sep 26, 2016

Conversation

minrk
Copy link
Member

@minrk minrk commented Aug 11, 2016

Redisplays output, returns reply.

Can be used in #184

Closes #176

cc @dsblank

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I didn't really like calling this run, since I think this is how execute should behave on the blocking client, but that would be backward-incompatible.

@takluyver what do you think about BlockingClient adding a reply=True kwarg to trigger waiting for and returning a reply on every req/rep message? That way, the regular methods could be used without needing to come up with synonyms.

Captures output, returns reply
waits for and returns reply instead of msg_id
@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I like the reply=True better, since it's not creating new methods, just completing the loop started by the existing methods, so I went ahead and updated this PR.

None is not valid in this case
@dsblank
Copy link
Member

dsblank commented Aug 11, 2016

Thanks, @minrk ... this works pretty well! One thing I am having trouble with is input() should that be able to work?

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

@dsblank not the way I've implemented it, I didn't include support for stdin requests, but I can.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

@dsblank it handles input now.

Parameters
----------
""")
parts.append("""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit leary of the docstring munging, because I don't think we'll remember it when editing docstrings - e.g. if we add a note below 'returns', it will be chopped off. Not a big deal, though.

Copy link
Member Author

@minrk minrk Aug 11, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, me, too. I couldn't think of a better way, though. If you have a less gross idea, I'm all for it.

The main reason I did this is that I can't see a good way to get a nice signature for the wrapped functions. If I had a good signature, I'd be happier with a simpler "See KernelClient.method for more details...". It's easy to inherit the wrapped method's signature exactly, but I didn't see a good way to do that and add the reply, timeout arguments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're willing to depend on (or bundle) a backported version of the Py3 inspect.signature machinery for Py2, it should be relatively easy to add a couple of parameters to a signature and set the f.__signature__ attribute. IIRC, IPython's inspection will use that information even on Python 2, which is good enough for me.

@dsblank
Copy link
Member

dsblank commented Aug 11, 2016

Works great!

stream.write(content['text'])
elif msg_type in ('display_data', 'execute_result', 'error'):
if in_kernel:
session.send(socket, msg_type, content, parent=parent_header)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit uncomfortable about this, because it's making a general method aware of a certain specific context in which it may be running.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. What would you prefer? What I really want is for rich Jupyter display methods to be fully redisplayed when run in a an IPython kernel, and for plaintext output to be shown in a simple terminal way when that's the case.

Do you think these should be different methods, or explicit user-provided flags?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would actually make this back to being a separate method. I think the kw arg works for the general reply=True to wait for the response, but this feels like another level on top of what the kernel client methods usually do. Maybe execute_interactive()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me. I'll leave execute(reply=True) matching the other methods, without touching output.

@dsblank
Copy link
Member

dsblank commented Aug 11, 2016

@minrk This issue is also a problem for non-ipython kernels. That is, we have to monkeypatch IPython.display.display in order to get rich display. If a kernel had a special method for this, then in_kernel and non-ipython kernels could both call it.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

@dsblank good point. What do you folks think? Special method or argument for kernel-aware behavior?

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

Honestly, I do think that the default behavior in IPython should be to redisplay rich output, so detecting IPython kernels by default still seems like the right choice to me. But I take @dsblank's point that to make it more generally useful, there should be some ways to pass explicit arguments in addition to that.

@dsblank
Copy link
Member

dsblank commented Aug 11, 2016

@minrk That makes sense (there has to be a default). Just remember that some languages will make this the interface to their language. As long as there is some method for dealing with the issue that is not too painful, that should work. Thanks for this!

output_hook is called with output messages,
so that other clients can use this (e.g. nbconvert)

Default is to redisplay, including detection of IPython kernel for rich outputs.
@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

It is now execute_interactive. It now takes an output_hook callable that will be passed all output messages. The default is still to redisplay, including detection of IPython, but passing the hook lets the caller decide what to do with all IOPub messages. I think that should be enough that we can use this in nbconvert --execute now, as well as allow uses in any other kernel context.

@takluyver
Copy link
Member

I guess it should have a stdin_hook as well, then.

Pfff, this is growing complexity rapidly. I think I'd be inclined to say it's intended as a terminal method, stick to displaying text output, and maybe offer a hook just for rich output (display_data/execute_result).

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I think I'd be inclined to say it's intended as a terminal method,

I would disagree with this, since the main reason I want this is to use it for interacting with kernels from notebooks. jupyter-run is just one entry point, but not the primary one, for me.

@minrk minrk changed the title add BlockingKernelClient.run add BlockingKernelClient.execute_interactive Aug 11, 2016
so that input request handling can be customized
@takluyver
Copy link
Member

Fair enough. I'm just disappointed that something that originally seemed quite simple has now ballooned into a fair amount of extra complexity.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

Fair enough. I don't feel bad, though, as the extra complexity should also mean that it ought to be able to replace a bunch of similar code in existing projects (nbconvert, jupyter_console, dask) once this is out. So to me it's more a consolidation of code to where it belongs.

@takluyver takluyver added this to the 5.0 milestone Aug 11, 2016
allow_stdin = self.allow_stdin
if allow_stdin and not self.stdin_channel.is_alive():
raise RuntimeError("stdin channel must be running to allow input")
msg_id = super(BlockingKernelClient, self).execute(code,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be a super() call now that it's a separate method, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed.

@takluyver
Copy link
Member

Other than that comment, this looks OK. I've milestoned it for 5.0 as it's clearly a new feature, but I won't get upset if we want to call it 4.4.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

Since 4.4 is about ready to ship, I'm happy calling it 4.5. I don't know that we have any breakages planned that would cause 5.0 in the forseeable future, though.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

(If there is one, 5.0 is fine, too)

@takluyver takluyver modified the milestones: 4.5, 5.0 Aug 11, 2016
@takluyver
Copy link
Member

Fair enough. I'm never entirely sure whether 4.x+1 releases are feature releases or bugfix releases.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I'm never entirely sure whether 4.x+1 releases are feature releases or bugfix releases.

We are on minor releases until there's a big and/or breaking change that prompt the major version bump. This doesn't happen often on the smaller repos, so I don't expect to have things like backport branches of ipykernel or jupyter_client or nbformat. It happens almost immediately on the big, fast repos like IPython and notebook, but there aren't many of those.

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I made jupyter/nbconvert#360 to see if this works over there. It's almost enough, but there are two features over there that I haven't implemented here:

  1. flushing output on its own, e.g. calling again after timeout (perhaps this should be split into a dedicated method)
  2. a separate timeout that just checks the time between outputs, which better detects 'stuck' kernels vs long-running code that keeps producing output

@minrk
Copy link
Member Author

minrk commented Aug 11, 2016

I can save those for another PR, though

@ivan-krukov
Copy link

ivan-krukov commented Sep 7, 2016

Is there a way that this can be used to echo input and traceback in a remote client?

#example.py
from jupyter_client import find_connection_file, BlockingKernelClient
kc = BlockingKernelClient(connection_file = find_connection_file())
kc.load_connection_file()
kc.execute_interactive('a=2')

Maybe I am missing the point, but running python example.py with an active jupyter console does not echo any output. It does produce produce a with a value of 2 in the target kernel, though.
I am more interested in tracebacks (via output_hook), but this is a starting point.

@minrk
Copy link
Member Author

minrk commented Sep 8, 2016

@ivan-krukov the code you gave has no output, so I wouldn't expect to see any. When I run something that has an exception, I do see it:

from jupyter_client import find_connection_file, BlockingKernelClient
kc = BlockingKernelClient(connection_file = find_connection_file())
kc.load_connection_file()
kc.execute_interactive('1/0')

produces:

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-5-969594e39729> in <module>()
----> 1 a/0

ZeroDivisionError: division by zero

@ivan-krukov
Copy link

@minrk Thanks.
I was expecting to see the output in the console of the target kernel. I assume that can be done by posting to iopub channel (with having ZMQTerminalInteractiveShell.include_other_output=True)?

@minrk
Copy link
Member Author

minrk commented Sep 9, 2016

Yes, the outputs are posted to IOPub, so they will appear in the console if that flag is specified, but not immediately. Since this was originally written against readline, we could only check for outputs after each prompt, and with prompt-toolkit it looks like we are now only checking on each execution. However, because prompt-toolkit has a proper eventloop, we should be able to handle it better than before. I opened jupyter/jupyter_console#105 to track this.

@minrk
Copy link
Member Author

minrk commented Sep 12, 2016

Since 4.4 is out, I'll merge this soon unless people want more time to review.

@minrk minrk merged commit 863ead4 into jupyter:master Sep 26, 2016
@minrk minrk deleted the KernelClient.run branch September 26, 2016 13:06
@minrk minrk modified the milestones: 4.5, 5.0 Feb 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants